Является ли ключевое слово Async await эквивалентом лямбда-выражения ContinueWith?

81

Может ли кто-нибудь быть достаточно любезным, чтобы подтвердить, правильно ли я понял ключевое слово Async await? (Используется версия 3 ОСАГО)

До сих пор я выяснил, что вставка ключевого слова await перед вызовом метода, по сути, делает 2 вещи: A. Он создает немедленный возврат и B. Он создает «продолжение», которое вызывается после завершения вызова асинхронного метода. В любом случае продолжение - это оставшаяся часть блока кода для метода.

Так что мне интересно, являются ли эти два бита кода технически эквивалентными, и если да, означает ли это в основном, что ключевое слово await идентично созданию ContinueWith Lambda (то есть: это в основном ярлык компилятора для одного)? Если нет, то в чем разница?

bool Success =
    await new POP3Connector(
        "mail.server.com", txtUsername.Text, txtPassword.Text).Connect();
// At this point the method will return and following code will
// only be invoked when the operation is complete(?)
MessageBox.Show(Success ? "Logged In" : "Wrong password");

VS

(new POP3Connector(
    "mail.server.com", txtUsername.Text, txtPassword.Text ).Connect())
.ContinueWith((success) =>
    MessageBox.Show(success.Result ? "Logged In" : "Wrong password"));
Максим Гершкович
источник

Ответы:

83

Общая идея правильная - оставшаяся часть метода сделана своего рода продолжением.

В сообщении в блоге "Fast path" есть подробности о том, как работает преобразование async/ awaitcompiler.

Различия, вне моей головы:

awaitКлючевое слово также использует понятие «контекст планирования». Контекст планирования - SynchronizationContext.Currentэто откат, если он существует TaskScheduler.Current. Затем продолжение выполняется в контексте планирования. Таким образом, ближе приближение будет проходить TaskScheduler.FromCurrentSynchronizationContextв ContinueWith, отступая на в TaskScheduler.Currentслучае необходимости.

Фактическая async/ awaitреализация основана на сопоставлении с образцом; он использует «ожидающий» шаблон, который позволяет ожидать другие вещи помимо задач. Некоторыми примерами являются асинхронные API WinRT, некоторые специальные методы, такие как Yieldнаблюдаемые Rx, и специальные ожидаемые сокеты, которые не так сильно влияют на сборщик мусора . Задачи - это мощные инструменты, но они не единственные, кого можно ожидать.

На ум приходит еще одно незначительное отличие: если ожидаемое уже выполнено, то в этот момент asyncметод фактически не возвращает; он продолжается синхронно. Так что это вроде как прохождение TaskContinuationOptions.ExecuteSynchronously, но без проблем, связанных со стеком.

Стивен Клири
источник
2
очень хорошо сказано - я стараюсь полагаться на сообщения Джона, поскольку они намного более обширны, чем что-либо, что у меня было бы время, чтобы дать ответ по SO, но Стивен абсолютно прав. WRT, чего ждать (и GetAwaiter в частности), его пост №3 очень полезен ИМХО :) msmvps.com/blogs/jon_skeet/archive/2011/05/13/…
Джеймс Мэннинг,
4
Место Стивена здесь. Для простых примеров легко подумать, что async / await - это просто ярлык для ContinueWith, однако мне нравится думать об этом наоборот. Async / await на самом деле является более мощным выражением того, для чего вы использовали ContinueWith. Проблема в том, что ContinueWith (...) использует лямбды и позволяет передавать выполнение в продолжение, но другие концепции потока управления, такие как циклы, практически невозможны, если вам нужно поместить половину тела цикла перед ContinueWith (.. .), а вторая половина - после. В итоге вы получите цепочку продолжений вручную.
Тео Яунг
7
Другой пример, когда async / await намного выразительнее, чем ContinueWith (...), - это поток исключений. Вы можете ждать несколько раз в одном блоке try, и для каждого этапа выполнения их исключения могут быть перенаправлены в один и тот же блок catch (...) без необходимости писать тонны кода, который делает это явно.
Тео Яунг,
2
Последняя часть async / await, которая примечательна, заключается в том, что это «концепция более высокого уровня», тогда как ContinueWith (...) является более ручной и явно имеет лямбды, создание делегатов и т. Д. С концепциями более высокого уровня больше возможностей для оптимизации - так что для Например, несколько ожиданий в одном методе фактически "разделяют" одно и то же замыкание лямбда (это как накладные расходы одной лямбды), тогда как ContinueWith (...) получает накладные расходы каждый раз, когда вы его вызываете, потому что вы явно написали лямбда, так что компилятор дает вам это.
Тео Яунг,
1
@MobyDisk: чтобы уточнить, по- awaitпрежнему захватывает так SynchronizationContext.Currentже, как и всегда. Но в ASP.NET Core SynchronizationContext.Currentесть null.
Стивен Клири
8

Это «по сути», но сгенерированный код делает больше, чем просто это. Чтобы получить более подробную информацию о сгенерированном коде, я настоятельно рекомендую серию Eduasync Джона Скита:

http://codeblog.jonskeet.uk/category/eduasync/

В частности, пост №7 рассказывает о том, что генерируется (начиная с CTP 2) и почему, поэтому, вероятно, он отлично подходит для того, что вы ищете в данный момент:

http://codeblog.jonskeet.uk/2011/05/20/eduasync-part-7-generated-code-from-a-simple-async-method/

РЕДАКТИРОВАТЬ: Я думаю, что это будет больше деталей, чем то, что вы ищете из вопроса, но если вам интересно, как выглядят вещи, когда у вас есть несколько ожиданий в методе, это описано в сообщении # 9 :)

http://codeblog.jonskeet.uk/2011/05/30/eduasync-part-9-generated-code-for-multiple-awaits/

Джеймс Мэннинг
источник