Comments 18
Контекст синхронизации есть только у GUI-приложений, таких как WinForms или WPF,
Это неправда. встроенный контекст синхронизации есть в тех приложениях, где он оправдан (например, помимо перечисленных, он еще есть в asp.net). Но при этом можно создать и собственный контекст, если мы хотим получить какую-то его функциональность.
после оператора await поток создается другой
Это далеко не обязательно, и определяется конкретным используемым диспетчером.
Поэтому дабы избавиться от сложностей работы с этим контекстом
А там есть какие-то сложности? В большей части случаев он работает так, как и ожидается программистом. Поправки надо делать только в тех случаях, если вы нарушаете правило async all the way down.
Ну и да, вы всегда можете отказаться от возврата в контекст синхронизации.
Ну скажем так — asp.net тоже в некотором роде GUI
Нет, asp.net — это веб-фреймворк. И, скажем, когда на нем пишут WebAPI, никам UI там вообще не пахнет.
Вы можете привести конкретный пример когда при применении await не создается ожидающий callback поток?
В такой формулировке — никогда, вся прелесть TPL в том, что никто никаких коллбэков не ожидает, это continuation-passing. Но даже если предположить, что вы, на самом деле, спрашиваете про потоки, в которых выполняются continuations, то вот вам простейший пример:
private static async Task MainAsync()
{
WriteCurrentThread();
await ImplAsync();
WriteCurrentThread();
}
private static async Task ImplAsync()
{
WriteCurrentThread();
}
Окей, пример надуманный (хотя, кстати, часто встречающийся в реальной жизни, именно поэтому в стейт-машине await
под него есть оптимизация). Ладно, давайте его немного усложним:
private static async Task MainAsync()
{
WriteCurrentThread();
using (var r = new StreamReader(new FileStream("F:\\fp.mpcpl ", FileMode.Open, FileAccess.Read)))
{
await r.ReadToEndAsync();
}
WriteCurrentThread();
}
Казалось бы, все, теперь мы всегда попадаем в другой поток… Но нет. Просто поменяем вызов c MainAsync().Wait()
на AsyncContext.Run((Func<Task>) MainAsync)
— и "волшебным" образом все continuations опять оказываются в одном потоке.
И вообще — как влияет диспетчера на работу await?
Извиняюсь, был не прав — не диспетчер, а шедулер.
Сложности работы с контекстом синхронизации состоят в том что его вообще приходиться использовать
Неа, в await
это происходит прозрачно:
//some code accessing HttpContext.Current
await SomeInternalCode();
//some other code accessing HttpContext.Current
Вы явное использование контекста синхронизации видите? А тем не менее, контекст есть, и работает. Более того, если его убрать, у программиста просто не будет способа достать HttpContext.Current
после await
.
скажем так, это вопрос терминологии
Терминология — штука специально весьма конкретная.
а кто сказал что asp.net — это UI
Вы: "asp.net тоже в некотором роде GUI".
вот что я имел в виду — asp.net имеет пул, и в этом он сходен с UI — у которого есть очередь обрабатывающая события окна
Намешали. Что значит, "asp.net имеет пул"? Если вы о том, что каждый приходящий запрос попадает в поток из определенного пула, то это свойство хоста, а не asp.net. А уж сходства между очередью событий и пулом нет вовсе — очередь событий в Windows Forms/WPF однопоточна (собственно, она для того и сделана, чтобы не заниматься синхронизацией на интерфейсных элементах), а обработчик http-запросов в asp.net в хорошем случае может быть вообще неблокирующим (если потоков хватает и не используется сессия).
И именно поэтому он имеет тот же механизм унификации возврата из асинхронного вызова.
"Механизм унификации возврата" имеет TPL, и этот механизм называется SynchronizationContext
. Вопрос только в том, какие хосты (и почему) по умолчанию запускают код в таком контексте, а какие — нет.
// здесь будет один поток ( скорее всего вызывающий)
WriteCurrentThread();
await ImplAsync();
// а вот здесь уже будет другой
WriteCurrentThread();
Эксперимент с вами не согласен:
static void Main(string[] args)
{
MainAsync().Wait();
}
private static async Task MainAsync()
{
WriteCurrentThread("MainAsync1");
await ImplAsync();
WriteCurrentThread("MainAsync2");
}
private static async Task ImplAsync()
{
WriteCurrentThread("ImplAsync");
}
Вывод:
MainAsync1: 1
ImplAsync: 1
MainAsync2: 1
конечно есть, только тут используется ExecutionContext, внутри которого и будет искомый HttpContext.Current. Но это не есть использование контекста синхронизации
Правда? А почему же тогда в методе SetContinuationForAwait
— а именно он в итоге отвечает за то, как будет вызван continuation после await
— в первую очередь используется SynchronizationContext.CurrentNoFlow
?
Здесь четко описана разница http://alz-it.blogspot.ru/2016/06/executioncontext-synchronizationcontext.html
Вы, когда переводите, пусть и с сокращениями, чужую статью — хоть бы на первоисточник ссылку давали. В оригинале явно написано:
When you await a task, by default the awaiter will capture the current SynchronizationContext, and if there was one, when the task completes it’ll Post the supplied continuation delegate back to that context, rather than running the delegate on whatever thread the task completed or rather than scheduling it to run on the ThreadPool.
это не единственный источник для моей статьи http://alz-it.blogspot.ru/2016/06/executioncontext-synchronizationcontext.html
Но один из основных (вплоть до дословного цитирования примеров кода).
если уж цитируете статью — то цитируйте хотя бы логически полностью, ибо абзацем выше сказано [...] и смысл как бы немного меняется
Нет, не меняется. Когда awaiter размещает continuation, он смотрит на SynchronizationContext напрямую, минуя ExecutionContext. Более того, когда ExecutionContext передается через await
, SynchronizationContext не захыватывается.
Поймите, ExecutionContext и SynchronizationContext в контексте (простите) await
— ортогональны. ExecutionContext отвечает за то, какие данные будут видны коду после await
, а SynchronizationContext — за то, где и когда код после await
будет выполнен.
про точки прерывания мне даже неудобно как-то говорить…
А при чем тут "точки прерывания"?
но призывает немного по другому взглянуть на привычные вещи, позволяет писать безопасный и легко тестируемый с точки зрения многопоточности код.
Вы где-то кроме программирования State Machine пытались применить это решение? Насколько я понял вы отказались от использования асинхронного кода в связке с .NET ThreadPool — в пользу создания создания отдельного потока на каждый контекст и последовательного выполнения задач в нем. Для кастомизации и тюнинга таких низкоуровневых решений требуеться намного больше знания многопоточности, чем для обычной синхронизации кода с использованием async\await, и которые не требуют девелопера считать сколько и когда им создавать потоков на приложение\event bus\операцию.
Событийная модель на основе async и await