2013-09-23 3 views
73

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

Await:

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

Если вы удалили вызов 'Wait' во втором примере *, то * два фрагмента были бы (в основном) эквивалентными. – Servy

+1

Возможный дубликат [Is Async ожидает ключевое слово, эквивалентное продолжению с лямбдой?] (Http://stackoverflow.com/questions/8767218/is-async-await-keyword-equivalent-to-a-continuewith-lambda) –

+0

FYI: Ваш метод getWebPage не может использоваться в обоих кодах. В первом коде он имеет тип «Задача », а во втором - тип «string». поэтому в основном ваш код не компилируется. - если быть точным. –

ответ

61

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

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

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

+0

Обработка ошибок между двумя фрагментами также отличается; обычно легче работать с 'await' над' ContinueWith' в этом отношении. – Servy

+0

@Servy: Верно, что-то добавит. –

+1

Планирование также совершенно иное, то есть, какой контекст 'parseData' выполняет. –

61

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

Предположим, у вас есть обработчик событий в приложении на основе графического интерфейса, для которого требуется много времени, и поэтому вы хотите сделать его асинхронным. Вот синхронная логика вы начинаете с:

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 Task AsyncLoop() { 
    while (true) { 
     string result = await LoadNextItem(); 
     if (result.Contains("target")) { 
      Counter.Value = result.Length; 
      break; 
     } 
    } 
} 

Это намного приятнее сейчас, не так ли?

+0

Спасибо, очень хорошее объяснение –

+0

Это отличный пример –

+0

это все еще обновляет пользовательский интерфейс. http://pastebin.com/Y8NddKp5 – MonsterMMORPG

Смежные вопросы