C# 5 Async/Await - это * одновременный *?

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

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

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

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

4 ответа

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

По умолчанию, 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 использовался.

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

Настоящая "мясная" (асинхронная) часть 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 иметь асинхронный результат)

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

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

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

Мне кажется, что людям нужен более простой ответ на этот вопрос. Так что я собираюсь слишком упростить.

Дело в том, что если вы сохраняете Задачи и не ожидаете их, тогда async/await будет "параллельным".

var a = await LongTask1(x);
var b = await LongTask2(y);

var c = ShortTask(a, b);

не является одновременным. LongTask1 завершится до запуска LongTask2.

var a = LongTask1(x);
var b = LongTask2(y);

var c = ShortTask(await a, await b);

одновременно.

Хотя я также призываю людей глубже понять и прочитать об этом, вы можете использовать async/await для параллелизма, и это довольно просто.

Другие вопросы по тегам