Разница между await и ContinueWith

может кто-нибудь объяснить, если await и ContinueWith являются синонимами или нет в следующем примере. Я пытаюсь использовать TPL в первый раз и читал всю документацию, но не понимаю разницы.

ждут:

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

ContinueWith:

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

один предпочтительнее другого в конкретных ситуациях?

2 ответов


во втором коде вы синхронно ожидание завершения продолжения. В первой версии метод вернется к вызывающему объекту, как только он достигнет первого await выражение, которое еще не завершено.

они очень похожи в том, что они оба планируют продолжение, но как только поток управления становится даже немного сложным,await ведет к много более простой код. Кроме того, как отметил Servy в комментариях, в ожидании задача будет" разворачивать " агрегированные исключения, что обычно приводит к более простой обработке ошибок. Также используя await неявно запланирует продолжение в вызывающем контексте (если вы не используете ConfigureAwait). Это ничего, что нельзя сделать "вручную", но это намного проще сделать с await.

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


вот последовательность фрагментов кода, которые я недавно использовал, чтобы проиллюстрировать разницу и различные проблемы с использованием async.

Предположим, у вас есть обработчик событий в вашем 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;
    }
});

отлично, теперь у нас есть метод, который не блокирует! Вместо этого он падает. Любые обновления элементов управления UI должны происходить в потоке UI, поэтому вам нужно объясните это. К счастью, есть возможность указать, как должны быть запланированы продолжения, и есть один по умолчанию для этого:

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;
        }
    }
}

теперь это намного приятнее, не так ли?