Чи є ключове слово Async await рівносильним лямбда-сигналу ContinueWith?


81

Чи може хтось, будь ласка, підтвердити, чи правильно я зрозумів ключове слово Async await? (Використання версії 3 CTP)

До цього часу я розробив, що вставлення ключового слова 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

Загальна ідея правильна - залишок методу перетворюється на продовження роду.

У дописі "швидкий шлях" є подробиці про те, як працює перетворення async/ awaitкомпілятор.

Відмінності, від маківки:

awaitКлючове слово також використовує поняття «контекст планування». Контекст планування, SynchronizationContext.Currentякщо він існує, повертається назад TaskScheduler.Current. Потім продовження виконується в контексті планування. Отже, ближче наближення було б перейти TaskScheduler.FromCurrentSynchronizationContextв ContinueWith, повернувшись за TaskScheduler.Currentнеобхідності.

Фактична async/ awaitреалізація базується на відповідності шаблону; він використовує "очікуваний" шаблон, який дозволяє чекати інших речей, крім завдань. Деякі приклади - це асинхронні API WinRT, деякі спеціальні методи, такі як Yieldспостережувані Rx, та спеціальні сокети, які не вражають GC так сильно . Завдання потужні, але це не єдине, що можна очікувати.

Пам’ятається ще одна незначна різниця: якщо очікуване вже завершено, тоді asyncметод фактично не повертається в цей момент; воно триває синхронно. Тож це як би проходження TaskContinuationOptions.ExecuteSynchronously, але без проблем, пов’язаних зі стеком.


2
дуже добре сказано - я намагаюся відкласти до дописів Джона, оскільки вони набагато обширніші, ніж будь-що, що я встиг би дати відповідь на SO, але Стівен абсолютно правий. WRT, що очікується (і GetAwaiter зокрема), його пост # 3 дуже корисний IMHO :) msmvps.com/blogs/jon_skeet/archive/2011/05/13/…
Джеймс Меннінг

4
Місце Стівена тут. Для простих прикладів легко подумати, що async / await - це лише ярлик для ContinueWith - однак, я люблю думати про це навпаки. Async / await насправді є більш потужним виразом того, для чого ви використовували ContinueWith. Проблема полягає в тому, що ContinueWith (...) використовує лямбди і дозволяє виконувати перенесення на продовження, але інші концепції потоку управління, такі як цикли, досить неможливі, якщо вам потрібно поставити половину тіла циклу перед ContinueWith (.. .), а друга половина після. У підсумку ви отримуєте ланцюжок продовження вручну.
Тео Яунг,

7
Інший приклад, коли async / await є набагато виразнішим, ніж ContinueWith (...), - потік винятків. Ви можете чекати кілька разів в одному і тому ж блоці спроб, і для кожного етапу виконання їх винятки можуть бути спрямовані в один і той же блок 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/

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.