Async CTP - рекомендуемый подход для планирования задач - PullRequest
11 голосов
/ 06 января 2012

В настоящее время я работаю над в значительной степени асинхронным приложением, которое использует TAP. Каждый класс, у которого есть методы для порождения Task s, также содержит TaskScheduler Это позволяет нам выполнять явное планирование задач, что, как я понимаю, не так, как Microsoft использует Async CTP.

Единственная проблема, с которой я столкнулся при новом подходе (неявное планирование), заключается в том, что наша предыдущая философия всегда была «мы знаем, что продолжение всегда будет указывать их планировщик задач, поэтому нам не нужно беспокоиться о том, в каком контексте мы выполняем задание на ".

Отход от этого действительно немного беспокоит нас только потому, что он работал очень хорошо с точки зрения избежания тонких ошибок потоков, потому что для каждого бита кода мы можем видеть, что кодер запомнил, к какому потоку он относится. Если они пропустили указание планировщика задач, это ошибка.

Вопрос 1: Может ли кто-нибудь заверить меня, что неявный подход - хорошая идея? Я вижу так много проблем, представленных ConfigureAwait (false) и явным планированием в устаревшем / стороннем коде. Как я могу быть уверен, что мой «ожидающий» код всегда работает в потоке пользовательского интерфейса, например?

Вопрос 2: Итак, предположим, что мы удалили все TaskScheduler DI из нашего кода и начали использовать неявное планирование, как нам тогда установить планировщик задач по умолчанию? Как насчет изменения планировщика на полпути через метод, незадолго до ожидания дорогостоящего метода, а затем его повторной установки?

(с. Я уже прочитал http://msmvps.com/blogs/jon_skeet/archive/2010/11/02/configuring-waiting.aspx)

1 Ответ

10 голосов
/ 06 января 2012

Я попробую ответить. ;)

Вопрос 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).
...