Різниця між очікуванням та ContinueWith


119

Чи може хтось пояснити, чи є awaitі ContinueWithє синонімами чи не в наступному прикладі. Я намагаюся використовувати TPL вперше і читаю всю документацію, але не розумію різниці.

Чекайте :

String webText = await getWebPage(uri);
await parseData(webText);

ПродовжитиЗ :

Task<String> webText = new Task<String>(() => getWebPage(uri));
Task continue = webText.ContinueWith((task) =>  parseData(task.Result));
webText.Start();
continue.Wait();

Чи один віддає перевагу іншому в конкретних ситуаціях?


3
Якщо ви видалили Waitвиклик у другому прикладі , то два сниппет будуть ( в основному) еквівалентні.
Сервіс


FYI: Ваш getWebPageметод не можна використовувати в обох кодах. У першому коді він має Task<string>тип повернення, а в другому - stringтип повернення. тому в основному ваш код не компілюється. - якщо бути точним.
Рой Намір

Відповіді:


101

У другому коді ви синхронно чекаєте завершення продовження. У першій версії метод повернеться до абонента, як тільки він потрапить на перший awaitвираз, який вже не завершено.

Вони дуже схожі тим, що обидва планують продовження, але як тільки потік управління стає ще трохи складним, awaitпризводить до набагато простішого коду. Крім того, як зауважив Сервій у коментарях, очікування завдання "відкрутить" сукупні винятки, що, як правило, призводить до більш простої обробки помилок. Також використання awaitбуде неявно планувати продовження в контексті виклику (якщо ви не використовуєте ConfigureAwait). Це нічого не можна зробити "вручну", але зробити це набагато простіше await.

Я пропоную вам спробувати здійснити трохи більшу послідовність операцій з обома awaitі Task.ContinueWith- це може бути справжнім відкривачем очей.


2
Обробка помилок між двома фрагментами також відрізняється; це взагалі простіше працювати з awaitбільш ContinueWithв цьому відношенні.
Сервіс

@Servy: Правда, додамо щось навколо.
Джон Скіт

1
Планування також зовсім інше, тобто те, в якому контексті parseDataвиконується.
Стівен Клірі

Коли ви скажете, що використання "await" неявно планує продовження в контексті виклику , чи можете ви пояснити вигоду від цього і що відбувається в іншій ситуації?
Гаррісон

4
@Harrison: Уявіть, що ви пишете додаток WinForms - якщо ви пишете метод асинхронізації, за замовчуванням весь код у методі буде працювати у потоці інтерфейсу, оскільки там буде заплановано продовження. Якщо ви не вказуєте, де ви хочете продовжувати запуск, я не знаю, що це за замовчуванням, але це може легко закінчитися запуском потоку пулу потоків ... в цей момент ви не можете отримати доступ до інтерфейсу користувача тощо .
Джон Скіт

100

Ось послідовність фрагментів коду, яку я нещодавно використовував, щоб проілюструвати різницю та різні проблеми за допомогою асинхронних рішень.

Припустимо, у вашому додатку на основі GUI є якийсь обробник подій, який займає багато часу, і тому ви хочете зробити його асинхронним. Ось синхронна логіка, з якої ви починаєте:

while (true) {
    string result = LoadNextItem().Result;
    if (result.Contains("target")) {
        Counter.Value = result.Length;
        break;
    }
}

LoadNextItem повертає завдання, яке в підсумку призведе до певного результату, який ви хочете перевірити. Якщо поточний результат такий, який ви шукаєте, ви оновлюєте значення деякого лічильника в інтерфейсі і повертаєтесь з методу. В іншому випадку ви продовжуєте обробляти більше елементів з LoadNextItem.

Перша ідея для асинхронної версії: просто використовуйте продовження! І давайте на даний момент ігноруємо циклічну частину. Я маю на увазі, що може піти не так?

return LoadNextItem().ContinueWith(t => {
    string result = t.Result;
    if (result.Contains("target")) {
        Counter.Value = result.Length;
    }
});

Чудово, зараз у нас є метод, який не блокує! Він замість цього виходить з ладу. Будь-які оновлення елементів управління користувальницьким інтерфейсом повинні відбуватися в потоці користувальницького інтерфейсу, тому вам потрібно буде це врахувати. На щастя, є можливість вказати, як слід планувати продовження, і для цього є лише стандартний:

return LoadNextItem().ContinueWith(t => {
    string result = t.Result;
    if (result.Contains("target")) {
        Counter.Value = result.Length;
    }
},
TaskScheduler.FromCurrentSynchronizationContext());

Чудово, зараз у нас є метод, який не виходить з ладу! Замість цього він виходить з ладу. Продовження є окремими завданнями, їх статус не пов'язаний з попереднім завданням. Тож навіть якщо LoadNextItem помилки, абонент побачить лише завдання, яке було успішно виконано. Добре, тоді просто передайте виняток, якщо такий є:

return LoadNextItem().ContinueWith(t => {
    if (t.Exception != null) {
        throw t.Exception.InnerException;
    }
    string result = t.Result;
    if (result.Contains("target")) {
        Counter.Value = result.Length;
    }
},
TaskScheduler.FromCurrentSynchronizationContext());

Чудово, зараз це насправді працює. Для одного предмета. Тепер, як щодо цього циклу. Виявляється, рішення, еквівалентне логіці оригінальної синхронної версії, буде виглядати приблизно так:

Task AsyncLoop() {
    return AsyncLoopTask().ContinueWith(t =>
        Counter.Value = t.Result,
        TaskScheduler.FromCurrentSynchronizationContext());
}
Task<int> AsyncLoopTask() {
    var tcs = new TaskCompletionSource<int>();
    DoIteration(tcs);
    return tcs.Task;
}
void DoIteration(TaskCompletionSource<int> tcs) {
    LoadNextItem().ContinueWith(t => {
        if (t.Exception != null) {
            tcs.TrySetException(t.Exception.InnerException);
        } else if (t.Result.Contains("target")) {
            tcs.TrySetResult(t.Result.Length);
        } else {
            DoIteration(tcs);
        }});
}

Або замість усього перерахованого вище ви можете використовувати async, щоб зробити те саме:

async Task AsyncLoop() {
    while (true) {
        string result = await LoadNextItem();
        if (result.Contains("target")) {
            Counter.Value = result.Length;
            break;
        }
    }
}

Зараз це набагато приємніше, чи не так?


Дякую, дуже приємне пояснення
Elger Mensonides

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