С# 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 или обработчиков событий для завершения задачи.