等待与继续的区别

有人能解释一下 awaitContinueWith在下面的例子中是否是同义词吗。我第一次尝试使用 TPL,并且已经阅读了所有的文档,但是不明白其中的区别。

等待 :

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

继续 :

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

在某些特定情况下,一个人是否比另一个人更受欢迎?

70063 次浏览

在第二段代码中,您是 同步,正在等待继承完成。在第一个版本中,该方法将在遇到第一个尚未完成的 await表达式时立即返回给调用者。

它们非常相似,因为它们都安排了一个延续,但是只要控制流变得稍微复杂一点,await就会产生更简单的 很多代码。此外,正如 Servy 在注释中指出的,等待任务将“打开”聚合异常,这通常会导致更简单的错误处理。使用 await还将隐式地调度调用上下文中的延续(除非使用 ConfigureAwait)。没有什么是不能“手动”完成的,但是使用 await做起来要容易得多。

我建议你试着用 awaitTask.ContinueWith来实现一个稍微大一点的操作序列——这会让你大开眼界。

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

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

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

LoadNextItem返回一个 Task,这将最终产生一些您想要检查的结果。如果当前结果是您正在查找的结果,则需要更新 UI 上某个计数器的值,然后从方法返回。否则,您将继续处理来自 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;
}
}
}

现在好多了,不是吗?