С# 5 Async/Await - это * одновременный *?
Я рассматривал новый асинхронный материал на С# 5, и появился один конкретный вопрос.
Я понимаю, что ключевое слово await
- это аккуратный трюк компилятора/синтаксический сахар для реализации продолжения передачи, где остаток метода разбивается на Task
объекты и очереди в очереди, но где управление возвращается вызывающему методу.
Моя проблема в том, что я слышал, что в настоящее время это все в одном потоке. Означает ли это, что этот асинхронный материал - это всего лишь способ превратить код продолжения в объекты Task
, а затем вызвать Application.DoEvents()
после завершения каждой задачи до начала следующего?
Или я что-то упускаю? (Эта часть вопроса риторическая - я полностью понимаю, что мне что-то не хватает:))
Большое спасибо заранее.
Ответы
Ответ 1
Это одновременно, в том смысле, что многие выдающиеся асинхронные операции могут выполняться в любое время. Он может быть или не быть многопоточным.
По умолчанию await
запланирует продолжение к "текущему контексту выполнения". "Текущий контекст выполнения" определяется как SynchronizationContext.Current
, если он не является null
или TaskScheduler.Current
, если нет SynchronizationContext
.
Вы можете переопределить это поведение по умолчанию, вызвав ConfigureAwait
и передав false
для параметра continueOnCapturedContext
. В этом случае продолжение не будет возвращено в этот контекст выполнения. Обычно это означает, что он будет запущен в потоке threadpool.
Если вы не пишете библиотечный код, поведение по умолчанию - именно то, что вам нужно. WinForms, WPF и Silverlight (т.е. Все интерфейсы пользовательского интерфейса) поставляют SynchronizationContext
, поэтому продолжение выполняется в потоке пользовательского интерфейса (и может безопасно обращаться к объектам пользовательского интерфейса). ASP.NET также предоставляет SynchronizationContext
, который гарантирует, что продолжение выполняется в правильном контексте запроса.
Другие потоки (включая потоки threadpool, Thread
и BackgroundWorker
) не поставляют SynchronizationContext
. Таким образом, приложения консоли и службы Win32 по умолчанию не имеют SynchronizationContext
. В этой ситуации продолжения выполняются в потоках threadpool. Вот почему демон демонстраций Console с использованием await
/async
включает вызов Console.ReadLine
/ReadKey
или блокировку Wait
на Task
.
Если вам понадобится SynchronizationContext
, вы можете использовать AsyncContext
из моего Nito.AsyncEx библиотека; он в основном просто обеспечивает async
-совместимый "основной цикл" с помощью SynchronizationContext
. Я считаю это полезным для консольных приложений и модульных тестов (VS2012 теперь имеет встроенную поддержку для async Task
модульных тестов).
Для получения дополнительной информации о SynchronizationContext
см. статью моего февральского MSDN.
Ни в коем случае не DoEvents
или эквивалентный вызов; скорее, поток управления возвращается полностью, а продолжение (остальная часть функции) планируется запустить позже. Это гораздо более чистое решение, потому что оно не вызывает проблем с повторной регистрацией, как если бы вы использовали DoEvents
.
Ответ 2
Вся идея async/await заключается в том, что она успешно выполняет передачу продолжения и не выделяет новый поток для операции. Продолжение может происходить в новом потоке, оно может продолжаться в одном и том же потоке.
Ответ 3
Реальная "мясо" (асинхронная) часть async/await обычно выполняется отдельно, а связь с вызывающим выполняется через TaskCompletionSource. Как написано здесь http://blogs.msdn.com/b/pfxteam/archive/2009/06/02/9685804.aspx
Тип TaskCompletionSource служит для двух связанных целей, на которые ссылается его имя: оно является источником для создания задачи и источником для завершения этих задач. По существу, TaskCompletionSource выступает в роли производителя для задачи и ее завершения.
и пример достаточно ясен:
public static Task<T> RunAsync<T>(Func<T> function)
{
if (function == null) throw new ArgumentNullException("function");
var tcs = new TaskCompletionSource<T>();
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
T result = function();
tcs.SetResult(result);
}
catch(Exception exc) { tcs.SetException(exc); }
});
return tcs.Task;
}
Через TaskCompletionSource
у вас есть доступ к объекту Task
, который вы можете ожидать, но не через ключевые слова async/await, которые вы создали многопоточность.
Обратите внимание, что когда многие "медленные" функции будут преобразованы в синтаксис async/await, вам не нужно будет использовать TaskCompletionSource
очень много. Они будут использовать его внутри (но в конце где-то должен быть TaskCompletionSource
, чтобы получить асинхронный результат)
Ответ 4
Способ, которым я хотел бы объяснить это, заключается в том, что ключевое слово "ожидание" просто ждет завершения задачи, но приводит к выполнению вызывающего потока во время ожидания. Затем он возвращает результат Задачи и продолжает выполнение из инструкции после ключевого слова "ожидание" после завершения задачи.
Некоторые люди, которых я заметил, похоже, считают, что задача выполняется в том же потоке, что и вызывающий поток, это неверно и может быть доказано, если вы попытаетесь изменить элемент GUI Windows.Forms в методе, который ждет вызовов. Тем не менее, продолжение выполняется в вызывающем потоке, где это возможно.
Его просто аккуратный способ не иметь делегатов callback или обработчиков событий для завершения задачи.