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