2013-09-23 41 views
73

之间的区别是否有人可以解释awaitContinueWith在下例中是否同义。我第一次尝试使用TPL,并一直在阅读所有文档,但不明白其中的差异。等待和ContinueWith

等待

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

可能重复的[是异步等待关键字相当于ContinueWith拉姆达?](http://stackoverflow.com/questions/8767218/is-async-await-keyword-equivalent-to-a-continuewith-lambda) –

+0

供参考:您的'getWebPage'方法不能在两个代码中使用。在第一个代码中它有一个'Task '返回类型,而在第二个代码中它有'string'返回类型。所以基本上你的代码不能编译。 - 如果要精确。 –

回答

61

在第二个代码中,你同步等待继续完成。在第一个版本中,只要方法遇到尚未完成的第一个表达式,该方法就会返回给调用方。

他们是在非常相似,它们都安排的延续,但只要控制流甚至略有变得复杂,await导致简单的代码。此外,正如Servy在评论中指出的那样,等待任务将“解包”聚合异常,这通常会导致更简单的错误处理。同样使用await将隐式调度调用上下文中的延续(除非使用ConfigureAwait)。这不是“手动”无法完成的,但通过await可以轻松完成。

我建议你尝试执行操作的稍大序列既awaitTask.ContinueWith - 它可以是一个真正的大开眼界。

+0

这两个片段之间的错误处理也不同;在这方面,通过'ContinueWith'上的'await'通常更容易。 – Servy

+0

@Servy:真的,会增加一些东西。 –

+1

调度也是完全不同的,也就是'parseData'执行的上下文。 –

61

下面是我最近用来说明使用异步解决方案的差异和各种问题的代码片段序列。

假设您在基于GUI的应用程序中有一些事件处理程序花费了大量时间,因此您希望使其处于异步。这里是你开始的同步逻辑:

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

LoadNextItem返回一个Task,它最终会产生一些你想检查的结果。如果当前结果是您正在查找的结果,则更新UI上某个计数器的值,然后从该方法返回。否则,您将继续处理来自LoadNextItem的更多项目。

异步版本的第一个想法:只使用continuations!让我们暂时忽略循环部分。我的意思是,什么可能会出错?

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