Разница между 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;
}
}
}
теперь это намного приятнее, не так ли?