Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
Роль метода ConfigureAwait()
Теперь, когда вы лучше понимаете роль класса SynchronizationContext, пришло время раскрыть роль метода ConfigureAwait(). По умолчанию применение await к объекту Task приводит к использованию контекста синхронизации. При разработке приложений с графическим пользовательским интерфейсом (Windows Forms, WPF) именно такое поведение является желательным. Однако в случае написания кода приложения без графического пользовательского интерфейса накладные расходы, связанные с постановкой в очередь исходного контекста, когда в этом нет нужды, потенциально могут вызвать проблемы с производительностью приложения.
Чтобы увидеть все в действии, модифицируйте операторы верхнего уровня, как показано ниже:
Console.WriteLine(" Fun With Async ===>");
// Console.WriteLine(DoWork());
string message = await DoWorkAsync();
Console.WriteLine(message);
string message1 = await DoWorkAsync().ConfigureAwait(false);
Console.WriteLine(message1);
В исходном блоке кода применяется класс SynchronizationContext, поставляемый инфраструктурой (в данном случае средой .NET Core Runtime), что эквивалентно вызову ConfigureAwait(true). Во втором примере текущий контекст и планировщик игнорируются.
Согласно рекомендациям команды создателей .NET Core при разработке прикладного кода (Windows Forms, WPF и т.д.) следует полагаться на стандартное поведение, а в случае написания неприкладного кода (скажем, библиотеки) использовать вызов ConfigureAwait(false). Одним исключением является инфраструктура ASP.NET Core (рассматриваемая в части IX), где специальная реализация SynchronizationContext не создается; таким образом, вызов ConfigureAwait(false) не дает преимущества при работе с другими инфраструктурами.
Соглашения об именовании асинхронных методов
Конечно же, вы заметили, что мы изменили имя метода с DoWork() на DoWorkAsync(), но по какой причине? Давайте предположим, что новая версия метода по-прежнему называется DoWork(), но вызывающий код реализован так:
// Отсутствует ключевое слово await!
string message = DoWork();
Обратите внимание, что мы действительно пометили метод ключевым словом async, но не указали ключевое слово await при вызове DoWork(). Здесь мы получим ошибки на этапе компиляции, потому что возвращаемым значением DoWork() является объект Task, который мы пытаемся напрямую присвоить переменной типа string. Вспомните, что ключевое слово await отвечает за извлечение внутреннего возвращаемого значения, которое содержится в объекте Task. Поскольку await отсутствует, возникает несоответствие типов.
На заметку! Метод, поддерживающий await — это просто метод, который возвращает Task или Task<T>.
С учетом того, что методы, которые возвращают объекты Task, теперь могут вызываться в неблокирующей манере посредством конструкций async и await, в Microsoft рекомендуют (в качестве установившейся практики) снабжать имя любого метода, возвращающего Task, суффиксом Async. В таком случае разработчики, которым известно данное соглашение об именовании, получают визуальное напоминание о том, что ключевое слово await является обязательным, если они намерены вызывать метод внутри асинхронного контекста.
На заметку! Обработчики событий для элементов управления графического пользовательского интерфейса (вроде обработчика события Click кнопки), а также методы действий внутри приложений в стиле MVC, к которым применяются ключевые слова async и await, не следуют указанному соглашению об именовании.
Асинхронные методы, возвращающие void
В настоящий момент наш метод DoWorkAsync() возвращает объект Task, содержащий "реальные данные" для вызывающего кода, которые будут получены прозрачным образом через ключевое слово await. Однако что если требуется построить асинхронный метод, возвращающий void? Реализация зависит о того, нуждается метод в применении await или нет (как в сценариях "запустил и забыл").
Асинхронные методы, возвращающие void и поддерживающие await
Если асинхронный метод должен поддерживать await, тогда используйте необобщенный класс Task и опустите любые операторы return, например:
static async Task MethodReturningTaskOfVoidAsync()
{
await Task.Run(() => { /* Выполнить какую-то работу... */
Thread.Sleep(4_000);
});
Console.WriteLine("Void method completed");
// Метод завершен
}
Затем в коде, вызывающем этот метод, примените ключевое слово await:
MethodReturningVoidAsync();
Console.WriteLine("Void method complete");
Асинхронные методы, возвращающие void и работающие в стиле "запустил и забыл"
Если метод должен быть асинхронным, но не обязан поддерживать await и применяться в сценариях "запустил и забыл", тогда добавьте ключевое слово async и сделайте возвращаемым типом void, а не Task. Методы такого рода обычно используются для задач вроде ведения журнала, когда нежелательно, чтобы запись в журнал приводила к задержке выполнения остального кода.
static async void MethodReturningVoidAsync()
{
await Task.Run(() => { /* Выполнить какую-то работу... */
Thread.Sleep(4_000);
});
Console.WriteLine("Fire and forget void method completed");
// Метод завершен
}
Затем в коде, вызывающем этот метод, ключевое слово await не используется:
MethodReturningVoidAsync();
Console.WriteLine("Void method complete");
Асинхронные методы с множеством контекстов await
Внутри реализации асинхронного метода разрешено иметь множество контекстов await. Следующий код является вполне допустимым:
static async Task MultipleAwaits()
{
await Task.Run(() => { Thread.Sleep(2_000); });
Console.WriteLine("Done with first task!");
// Первая задача завершена!
await Task.Run(() => { Thread.Sleep(2_000); });
Console.WriteLine("Done with second task!");
// Вторая задача завершена!
await Task.Run(() => { Thread.Sleep(2_000); });
Console.WriteLine("Done with third task!");
// Третья задача завершена!
}
Здесь каждая задача всего лишь приостанавливает текущий поток на некоторый период времени; тем не менее, посредством таких задач может быть представлена любая единица работы (обращение к веб-службе, чтение базы данных или что-нибудь еще). Еще один вариант предусматривает ожидание не каждой отдельной задачи, а всех их вместе. Это более вероятный сценарий, когда имеются три работы (скажем, проверка поступления сообщений электронной почты, обновление сервера, загрузка файлов), которые должны делаться в пакете, но могут выполняться параллельно. Ниже приведен модифицированный код, в котором используется метод Task.WhenAll():
static async Task MultipleAwaits()
{
var task1 = Task.Run(() =>
{
Thread.Sleep(2_000);
Console.WriteLine("Done with first task!");
});