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

Может ли кто-нибудь быть достаточно любезным, чтобы подтвердить, правильно ли я понял ключевое слово 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"));

person Maxim Gershkovich    schedule 07.01.2012    source источник


Ответы (2)


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

Сообщение в блоге "быстрый путь" содержит подробную информацию о том, как работает преобразование компилятора _1 _ / _ 2_.

Различия, невдомек:

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

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

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

person Stephen Cleary    schedule 07.01.2012
comment
очень хорошо сказано - я стараюсь полагаться на сообщения Джона, поскольку они намного более обширны, чем что-либо, что у меня было бы время, чтобы дать ответ на SO, но Стивен абсолютно прав. WRT, чего ждать (и GetAwaiter в частности), его пост №3 очень полезен ИМХО :) msmvps.com/blogs/jon_skeet/archive/2011/05/13/ - person James Manning; 07.01.2012
comment
Место Стивена здесь. Для простых примеров легко подумать, что async / await - это просто ярлык для ContinueWith, однако мне нравится думать об этом в обратном порядке. Async / await на самом деле является более мощным выражением того, для чего вы использовали ContinueWith. Проблема в том, что ContinueWith (...) использует лямбды и позволяет передавать выполнение в продолжение, но другие концепции потока управления, такие как циклы, практически невозможны, если вам нужно поместить половину тела цикла перед ContinueWith (.. .), а вторая половина - после. В итоге вы получите цепочку продолжения вручную. - person Theo Yaung; 08.01.2012
comment
Другой пример, когда async / await намного выразительнее, чем ContinueWith (...), - это поток исключений. Вы можете ждать несколько раз в одном блоке try, и для каждого этапа выполнения их исключения могут быть перенаправлены в один и тот же блок catch (...) без необходимости писать тонны кода, который делает это явно. - person Theo Yaung; 08.01.2012
comment
Последняя часть async / await, которая примечательна, заключается в том, что это концепция более высокого уровня, тогда как ContinueWith (...) является более ручной и явно имеет лямбды, создание делегатов и т. Д. С концепциями более высокого уровня есть больше возможностей для оптимизации - так, например, несколько ожиданий в одном методе фактически используют одно и то же замыкание лямбда (это похоже на накладные расходы одной лямбда), тогда как ContinueWith (...) получает накладные расходы каждый раз, когда вы его вызываете, потому что вы явно написали лямбда, поэтому компилятор дает что вам. - person Theo Yaung; 08.01.2012
comment
Не могли бы вы подробнее рассказать, что произойдет, если этот Connect метод вызовет исключение в коде OPs? Будет ли ContinueWith уволен в таком случае? И если да, то есть ли лучший способ получить такое поведение, используя только _3 _ / _ 4_? Я хочу запускать две задачи одну за другой, и меня не волнует, генерирует ли первая исключение, вторая всегда должна выполняться. AFAICT, если бы я пошел с ожиданиями, мне пришлось бы явно обрабатывать исключения и создавать совокупное исключение. - person julealgon; 03.07.2015
comment
Если Connect бросил SpecificException, то ContinueWith выполнит; доступ к свойству Result вызовет AggregateException, оборачивающий SpecificException. Тогда это исключение будет проигнорировано. I want to run two tasks one after the other, and I don't care if the first one throws an exception, the second should always run. Для этого есть шаблон: try { await first(); } finally { await second(); }, который работает для C # 6 / VS2015, но на VS2013 вам придется try { await first(); } catch { } await second();. - person Stephen Cleary; 04.07.2015
comment
Обновление: использование SynchronizationContext.Current было изменено в ASP.NET Core. Так что, если вы читаете это, имея в виду ASP.NET Core, см. Блог .stephencleary.com / 2017/03 / - person Moby Disk; 08.05.2020
comment
@MobyDisk: Чтобы уточнить, await по-прежнему захватывает SynchronizationContext.Current, как и всегда. Но в ASP.NET Core SynchronizationContext.Current равно null. - person Stephen Cleary; 08.05.2020

По сути, это так, но сгенерированный код делает больше, чем просто это. Для получения более подробной информации о сгенерированном коде я настоятельно рекомендую серию 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/

person James Manning    schedule 07.01.2012