Язык программирования C#9 и платформа .NET5
using System;using System.Collections.Generic;using System.Threading;using System.Threading.Tasks;Console.WriteLine(" Fun With Async ===>");Console.WriteLine(DoWork());Console.WriteLine("Completed");Console.ReadLine();static string DoWork(){Thread.Sleep(5_000);return "Done with work!";}Вам известно, что после запуска программы придется ожидать пять секунд, прежде чем сможет произойти что-то еще. В случае графического приложения весь пользовательский интерфейс был бы заблокирован до тех пор, пока работа не завершится.
Если бы мы решили прибегнуть к одному из описанных ранее приемов, чтобы сделать приложение более отзывчивым, тогда пришлось бы немало потрудиться. Тем не менее, начиная с версии .NET 4.5, можно написать следующий код С#:
...string message = await DoWorkAsync();Console.WriteLine(message);...static string DoWork(){Thread.Sleep(5_000);return "Done with work!";}static <b>async Task</b><string> DoWorkAsync(){return <b>await</b> Task.Run(() =>{Thread.Sleep(5_000);return "Done with work!";});}Если вы используете в качестве точки входа метод
(вместо операторов верхнего уровня), тогда должны пометить метод с помощью ключевого словаMain(), появившегося в версии C# 7.1:asyncstatic <b>async</b> Task Main(string[] args){...string message = await DoWorkAsync();Conole.WriteLine(message);...}На заметку! Возможность декорирования метода
посредствомMain()— нововведение, появившееся в версии C# 7.1. Операторы верхнего уровня в версии C# 9.0 являются неявно асинхронными.asyncОбратите внимание на ключевое слово
перед именем метода, который будет вызван в неблокирующей манере. Это важно: если метод декорируется ключевым словомawait, но не имеет хотя бы одного внутреннего вызова метода с использованиемasync, то получится синхронный вызов (на самом деле компилятор выдаст соответствующее предупреждение).awaitКроме того, вы должны применять класс
из пространства именTaskдля переделки методовSystem.Threading.Tasks(если вы используетеMain()) иMain()(последний добавляется какDoWork()). По существу вместо возвращения просто специфического значения (объектаDoWorkAsync()в текущем примере) возвращается объектstring, где обобщенный параметр типаTask<T>представляет собой действительное возвращаемое значение.ТРеализация метода
теперь напрямую возвращает объектDoWorkAsync(), который является возвращаемым значениемTask<T>. МетодTask.Run()принимает делегатRun()илиFunc<>и, как вам уже известно, для простоты здесь можно использовать лямбда-выражение. В целом новая версияAction<>может быть описана следующим образом.DoWorkAsync()При вызове запускается новая задача, которая заставляет вызывающий поток уснуть на пять секунд. После завершения вызывающий поток предоставляет строковое возвращаемое значение. Эта строка помещается в новый объект
и возвращается вызывающему коду.Task<string>Благодаря новой реализации метода
мы можем получить некоторое представление о подлинной роли ключевого словаDoWorkAsync(). Оно всегда будет модифицировать метод, который возвращает объектawait. Когда поток выполнения достигаетTask, вызывающий поток приостанавливается до тех пор, пока вызов не будет завершен. Запустив эту версию приложения, вы обнаружите, что сообщениеawaitотображается перед сообщениемCompletedВ случае графического приложения можно было бы продолжать работу с пользовательским интерфейсом одновременно с выполнением методаDone with work!.DoWorkAsync()Класс SynchronizationContext и async/await
Тип
формально определен как базовый класс, который предоставляет свободный от потоков контекст баз синхронизации. Хотя такое первоначальное определение не особо информативно, в официальной документации указаны следующие сведения.SynchronizationContextЦель модели синхронизации, реализуемой классом
, заключается в том, чтобы позволить внутренним асинхронным/синхронным операциям общеязыковой исполняющей среды вести себя надлежащим образом с различными моделями синхронизации.SynchronizationContext