Язык программирования C#9 и платформа .NET5
Наряду с тем, что вам уже известно о многопоточности, такое заявление проливает свет на этот вопрос. Вспомните, что приложения с графическим пользовательским интерфейсом (Windows Forms, WPF) не разрешают прямой доступ к элементам управления из вторичных потоков, а требуют делегирования доступа. Вы уже видели объект
в примере приложения WPF. В консольных приложениях, которые не используют WPF, это ограничение отсутствует. Речь идет о разных моделях синхронизации. С учетом всего сказанного давайте рассмотрим классDispatcher.SynchronizationContextКласс
является типом, предоставляющим виртуальный метод отправки, который принимает делегат, предназначенный для выполнения асинхронным образом. В результате инфраструктуры получают шаблон для надлежащей обработки асинхронных запросов (диспетчеризация для приложений WPF/Windows Forms, прямое выполнение для приложений без графического пользовательского интерфейса и т.д.). Он предлагает способ постановки в очередь единицы работы в контексте и подсчета асинхронных операций, ожидающих выполнения.SynchonizationContextКак обсуждалось ранее, когда делегат помещается в очередь для асинхронного выполнения, он планируется к запуску в отдельном потоке, что обрабатывается средой .NET Core Runtime. Задача обычно решается с помощью управляемого пула потоков .NET Core Runtime, но может быть построена и специальная реализация.
Хотя такими связующими действиями можно управлять вручную в коде, шаблон
делает большую часть трудной работы. В случае примененияasync/awaitк асинхронному методу задействуются реализацииawaitиSynchronizationContextцелевой инфраструктуры. Например, если вы используетеTaskSchedulerв приложении WPF, то инфраструктура WPF обеспечит диспетчеризацию делегата и обратный вызов в конечном автомате при завершении ожидающей задачи, чтобы безопасным образом обновить элементы управления.async/awaitРоль метода ConfigureAwait()
Теперь, когда вы лучше понимаете роль класса
, пришло время раскрыть роль методаSynchronizationContext. По умолчанию применениеConfigureAwait()к объектуawaitприводит к использованию контекста синхронизации. При разработке приложений с графическим пользовательским интерфейсом (Windows Forms, WPF) именно такое поведение является желательным. Однако в случае написания кода приложения без графического пользовательского интерфейса накладные расходы, связанные с постановкой в очередь исходного контекста, когда в этом нет нужды, потенциально могут вызвать проблемы с производительностью приложения.TaskЧтобы увидеть все в действии, модифицируйте операторы верхнего уровня, как показано ниже:
Console.WriteLine(" Fun With Async ===>");// Console.WriteLine(DoWork());string message = await DoWorkAsync();Console.WriteLine(message);string message1 = await DoWorkAsync().ConfigureAwait(false);Console.WriteLine(message1);В исходном блоке кода применяется класс
, поставляемый инфраструктурой (в данном случае средой .NET Core Runtime), что эквивалентно вызовуSynchronizationContext. Во втором примере текущий контекст и планировщик игнорируются.ConfigureAwait(true)Согласно рекомендациям команды создателей .NET Core при разработке прикладного кода (Windows Forms, WPF и т.д.) следует полагаться на стандартное поведение, а в случае написания неприкладного кода (скажем, библиотеки) использовать вызов
. Одним исключением является инфраструктура ASP.NET Core (рассматриваемая в части IX), где специальная реализацияConfigureAwait(false)не создается; таким образом, вызовSynchronizationContextне дает преимущества при работе с другими инфраструктурами.ConfigureAwait(false)Соглашения об именовании асинхронных методов
Конечно же, вы заметили, что мы изменили имя метода с
наDoWork(), но по какой причине? Давайте предположим, что новая версия метода по-прежнему называетсяDoWorkAsync(), но вызывающий код реализован так:DoWork()// Отсутствует ключевое слово await!string message = DoWork();Обратите внимание, что мы действительно пометили метод ключевым словом
, но не указали ключевое слово await при вызовеasync. Здесь мы получим ошибки на этапе компиляции, потому что возвращаемым значениемDoWork()является объектDoWork(), который мы пытаемся напрямую присвоить переменной типаTask. Вспомните, что ключевое словоstringотвечает за извлечение внутреннего возвращаемого значения, которое содержится в объектеawait. ПосколькуTaskотсутствует, возникает несоответствие типов.awaitНа заметку! Метод, поддерживающий
— это просто метод, который возвращаетawaitилиTask.Task<T>С учетом того, что методы, которые возвращают объекты
, теперь могут вызываться в неблокирующей манере посредством конструкцийTaskиasync, в Microsoft рекомендуют (в качестве установившейся практики) снабжать имя любого метода, возвращающегоawait, суффиксомTask. В таком случае разработчики, которым известно данное соглашение об именовании, получают визуальное напоминание о том, что ключевое словоAsyncявляется обязательным, если они намерены вызывать метод внутри асинхронного контекста.awaitНа заметку! Обработчики событий для элементов управления графического пользовательского интерфейса (вроде обработчика события
кнопки), а также методы действий внутри приложений в стиле MVC, к которым применяются ключевые словаClickиasync, не следуют указанному соглашению об именовании.awaitАсинхронные методы, возвращающие void
В настоящий момент наш метод
возвращает объектDoWorkAsync(), содержащий "реальные данные" для вызывающего кода, которые будут получены прозрачным образом через ключевое словоTask. Однако что если требуется построить асинхронный метод, возвращающийawait? Реализация зависит о того, нуждается метод в примененииvoidили нет (как в сценариях "запустил и забыл").await