在以下示例中,有人可以解释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();
在特定情况下,一个人比其他人更受欢迎吗?
答案 0 :(得分:85)
在第二个代码中,您同步等待继续完成。在第一个版本中,该方法一旦到达第一个尚未完成的await
表达式,就会返回给调用者。
它们非常相似,因为它们都安排了延续,但是一旦控制流程变得稍微复杂,await
就会导致更多更简单的代码。此外,正如Servy在评论中指出的那样,等待任务将“解开”聚合异常,这通常会导致更简单的错误处理。同样使用await
将隐式调度调用上下文中的延续(除非您使用ConfigureAwait
)。没有什么是“手动”无法完成的,但使用await
进行操作要容易得多。
我建议您尝试使用await
和Task.ContinueWith
实施稍微大一点的操作序列 - 这可能会令人大开眼界。
答案 1 :(得分:83)
这是我最近使用的代码片段序列,用于说明使用异步求解的差异和各种问题。
假设您的基于GUI的应用程序中有一些事件处理程序需要花费大量时间,因此您希望将其设置为异步。这是你开始的同步逻辑:
while (true) {
string result = LoadNextItem().Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
break;
}
}
LoadNextItem返回一个Task,它最终会产生一些你想要检查的结果。如果当前结果是您要查找的结果,则更新UI上某个计数器的值,然后从该方法返回。否则,您将继续处理LoadNextItem中的更多项目。
异步版本的第一个想法:只使用continuation!让我们暂时忽略循环部分。我的意思是,可能出现什么问题?
return LoadNextItem().ContinueWith(t => {
string result = t.Result;
if (result.Contains("target")) {
Counter.Value = result.Length;
}
});
很好,现在我们有一个不阻止的方法!它反而崩溃了。 UI控件的任何更新都应该在UI线程上进行,因此您需要考虑到这一点。值得庆幸的是,有一个选项可以指定如何安排continuation,并且只有一个默认值:
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;
}
}
}
现在好多了,不是吗?