Я попробую ответить. ;)
Вопрос 1: Может ли кто-нибудь заверить меня, что неявный подход - хорошая идея? Я вижу так много проблем, представленных ConfigureAwait (false) и явным планированием в устаревшем / стороннем коде. Как я могу быть уверен, что мой «ожидающий» код всегда работает в потоке пользовательского интерфейса, например?
Правила для ConfigureAwait(false)
довольно просты: используйте его, если остальная часть вашего метода может быть запущена в пуле потоков, и не используйте его, если остальная часть вашего метода должна работать в заданном контексте (например, пользовательский интерфейс) контекст).
Вообще говоря, ConfigureAwait(false)
должен использоваться кодом библиотеки, а не кодом уровня UI (включая слои типа UI, такие как ViewModels в MVVM). Если метод является частично-фоновыми вычислениями и частично-обновлениями пользовательского интерфейса, то его следует разделить на два метода.
Вопрос 2: Итак, если мы удалим все DI TaskScheduler из нашего кода и начнем использовать неявное планирование, как нам тогда установить планировщик задач по умолчанию?
async
/ await
обычно не использует TaskScheduler
; они используют концепцию «планирования контекста». Это на самом деле SynchronizationContext.Current
и возвращается к TaskScheduler.Current
, только если нет SynchronizationContext
. Таким образом, замена вашего собственного планировщика может быть выполнена с помощью SynchronizationContext.SetSynchronizationContext
. Вы можете прочитать больше о SynchronizationContext
в этой статье MSDN на эту тему .
Контекст планирования по умолчанию должен быть тем, что вам нужно почти все время, что означает, что вам не нужно с ним связываться. Я изменяю его только при выполнении модульных тестов или для консольных программ / служб Win32.
Как насчет изменения планировщика на полпути через метод, непосредственно перед ожиданием дорогостоящего метода, а затем снова установить его обратно?
Если вы хотите выполнить дорогостоящую операцию (предположительно в пуле потоков), тогда await
результат TaskEx.Run
.
Если вы хотите изменить планировщик по другим причинам (например, параллелизм), тогда await
результат TaskFactory.StartNew
.
В обоих этих случаях метод (или делегат) запускается в другом планировщике, а затем остальная часть метода возобновляется в своем обычном контексте.
В идеале вы хотите, чтобы каждый метод async
существовал в одном контексте выполнения. Если существуют разные части метода, которые нуждаются в разных контекстах, разделите их на разные методы. Единственное исключение из этого правила - ConfigureAwait(false)
, которое позволяет методу запускаться в произвольном контексте и затем возвращаться к контексту пула потоков до конца его выполнения. ConfigureAwait(false)
следует рассматривать как оптимизацию (она включена по умолчанию для библиотечного кода), а не как философию проектирования.
Вот некоторые моменты из моего выступления «Thread is Dead», которые, я думаю, могут помочь вам в вашем дизайне:
- Следуйте указаниям асинхронного шаблона на основе задач.
- Поскольку ваша кодовая база станет более асинхронной, она станет более функциональной по своей природе (в отличие от традиционно объектно-ориентированной). Это нормально и должно быть принято.
- Поскольку ваша кодовая база становится более асинхронной, параллелизм совместно используемой памяти постепенно переходит в параллелизм передачи сообщений (т. Е.
ConcurrentExclusiveSchedulerPair
- это новый ReaderWriterLock
).