C # 5 Async / Await - это * одновременный *? - PullRequest
57 голосов
/ 05 октября 2011

Я рассматривал новый асинхронный материал в C # 5, и возник один конкретный вопрос.

Я понимаю, что ключевое слово await - это аккуратный трюк компилятора / синтаксический сахар для реализации прохождения продолжения , где оставшаяся часть метода разбита на Task объекты и поставлена ​​в очередь, чтобы быть выполняется по порядку, но когда управление возвращается вызывающему методу.

Моя проблема в том, что я слышал, что в настоящее время все это в одном потоке. Означает ли это, что этот асинхронный материал на самом деле является просто способом преобразования кода продолжения в Task объекты и последующего вызова Application.DoEvents() после завершения каждой задачи перед началом следующей?

Или я что-то упустил? (Эта часть вопроса риторическая - я полностью осознаю, что мне не хватает что-то :))

Ответы [ 4 ]

51 голосов
/ 05 октября 2011

Это одновременное , в том смысле, что многие невыполненные асинхронные операции могут выполняться в любое время. Может быть или не быть многопоточным .

По умолчанию await запланирует продолжение обратно в «текущий контекст выполнения». «Текущий контекст выполнения» определяется как SynchronizationContext.Current, если это не null, или TaskScheduler.Current, если нет SynchronizationContext.

Вы можете переопределить это поведение по умолчанию, вызвав ConfigureAwait и передав false для параметра continueOnCapturedContext. В этом случае продолжение не будет запланировано обратно в этот контекст выполнения. Обычно это означает, что он будет запущен в потоке потоков.

Если вы не пишете код библиотеки, поведение по умолчанию - именно то, что вам нужно. WinForms, WPF и Silverlight (то есть все инфраструктуры пользовательского интерфейса) предоставляют SynchronizationContext, поэтому продолжение выполняется в потоке пользовательского интерфейса (и может безопасно получать доступ к объектам пользовательского интерфейса). ASP.NET также предоставляет SynchronizationContext, который обеспечивает выполнение продолжения в правильном контексте запроса.

Другие потоки (включая потоки пулов потоков, Thread и BackgroundWorker) не предоставляют SynchronizationContext. Поэтому консольные приложения и службы Win32 по умолчанию вообще не имеют SynchronizationContext. В этой ситуации продолжения выполняются в потоках пула потоков. Вот почему демонстрационные версии консольных приложений, использующие await / async, включают вызов Console.ReadLine / ReadKey или блокировку Wait для Task.

.

Если вам нужен SynchronizationContext, вы можете использовать AsyncContext из моей библиотеки Nito.AsyncEx ; в основном это просто async -совместимый «основной цикл» с SynchronizationContext. Я считаю его полезным для консольных приложений и модульных тестов (VS2012 теперь имеет встроенную поддержку async Task модульных тестов).

Дополнительную информацию о SynchronizationContext см. В статье Моя февральская MSDN .

Никогда не вызывается DoEvents или его эквивалент; скорее поток управления возвращает полностью, а продолжение (остальная часть функции) планируется запустить позже. Это гораздо более чистое решение, потому что оно не вызывает проблем с повторным входом, как если бы вы использовали DoEvents.

5 голосов
/ 05 октября 2011

Идея async / await заключается в том, что он хорошо выполняет передачу продолжения и не выделяет новый поток для операции. Продолжение может встречаться в новом потоке, оно может продолжаться в том же потоке.

2 голосов
/ 05 октября 2011

Реальная «мясная» (асинхронная) часть 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, чтобы иметь асинхронный результат)

1 голос
/ 03 ноября 2011

Мне нравится объяснять это тем, что ключевое слово «await» просто ожидает завершения задачи, но возвращает выполнение вызывающему потоку, пока оно ожидает.Затем он возвращает результат Задачи и продолжает выполнение оператора после ключевого слова «await» после завершения Задачи.

Некоторые люди, которых я заметил, считают, что Задача запускается в том же потоке, что ивызывающий поток, это неверно и может быть доказано попыткой изменить элемент графического интерфейса Windows.Forms в методе, ожидающем вызова.Однако продолжение запускается в вызывающем потоке, где это возможно.

Это просто аккуратный способ не иметь делегатов обратного вызова или обработчиков событий для завершения задачи.

...