Литмир - Электронная Библиотека
A
A

Наряду с тем, что вам уже известно о многопоточности, такое заявление проливает свет на этот вопрос. Вспомните, что приложения с графическим пользовательским интерфейсом (Windows Forms, WPF) не разрешают прямой доступ к элементам управления из вторичных потоков, а требуют делегирования доступа. Вы уже видели объект

Dispatcher
в примере приложения WPF. В консольных приложениях, которые не используют WPF, это ограничение отсутствует. Речь идет о разных моделях синхронизации. С учетом всего сказанного давайте рассмотрим класс
SynchronizationContext
.

Класс

SynchonizationContext
является типом, предоставляющим виртуальный метод отправки, который принимает делегат, предназначенный для выполнения асинхронным образом. В результате инфраструктуры получают шаблон для надлежащей обработки асинхронных запросов (диспетчеризация для приложений WPF/Windows Forms, прямое выполнение для приложений без графического пользовательского интерфейса и т.д.). Он предлагает способ постановки в очередь единицы работы в контексте и подсчета асинхронных операций, ожидающих выполнения.

Как обсуждалось ранее, когда делегат помещается в очередь для асинхронного выполнения, он планируется к запуску в отдельном потоке, что обрабатывается средой .NET Core Runtime. Задача обычно решается с помощью управляемого пула потоков .NET Core Runtime, но может быть построена и специальная реализация.

Хотя такими связующими действиями можно управлять вручную в коде, шаблон

async/await
делает большую часть трудной работы. В случае применения
await
к асинхронному методу задействуются реализации
SynchronizationContext
и
TaskScheduler
целевой инфраструктуры. Например, если вы используете
async/await
в приложении WPF, то инфраструктура WPF обеспечит диспетчеризацию делегата и обратный вызов в конечном автомате при завершении ожидающей задачи, чтобы безопасным образом обновить элементы управления.

Роль метода 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
или нет (как в сценариях "запустил и забыл").

297
{"b":"847442","o":1}