等待和ContinueWith之间的区别


119

在下面的示例中,有人可以解释是否awaitContinueWith是同义词。我正在尝试第一次使用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();

在特定情况下,一个相对于其他优先吗?


3
如果您Wait在第二个示例中删除了该呼叫,这两个摘要将(大部分)等效。
Servy 2013年


仅供参考:您的getWebPage方法不能同时在两个代码中使用。在第一个代码中,它具有Task<string>返回类型,而在第二个代码中,其具有string返回类型。所以基本上您的代码无法编译。-如果准确的话。
罗伊·纳米尔

Answers:


101

在第二个代码中,您正在同步等待延续完成。在第一个版本中,该方法将在调用await尚未完成的第一个表达式时返回到调用方。

他们是,它们都计划延续很相似,但只要控制流变得稍微复杂,await导致很多简单的代码。此外,正如Servy在评论中指出的那样,等待任务将“解包”聚集异常,这通常会导致更简单的错误处理。也可以使用await来隐式安排调用上下文中的继续(除非您使用ConfigureAwait)。没什么可以“手动”完成的,但使用则容易得多await

我建议您尝试同时使用await和来实现稍大一些的操作序列Task.ContinueWith-这可能是真正的大开眼界。


2
两个摘要之间的错误处理也有所不同。它与一般较易工作awaitContinueWith这方面。
Servy 2013年

@Servy:是的,将在此周围添加一些内容。
乔恩·斯基特

1
调度也有较大差异,即什么情况下parseData执行英寸
斯蒂芬·克利

当您说使用await将在调用上下文中隐式安排继续时,您能否解释一下这样做的好处,以及在其他情况下会发生什么?
哈里森

4
@Harrison:假设您正在编写WinForms应用程序-如果编写异步方法,则默认情况下,该方法中的所有代码都将在UI线程中运行,因为将在该线程中安排继续。如果您不指定要在哪里运行延续,则我不知道默认值是什么,但是它很可能最终会在线程池线程上运行……这时您将无法访问UI,等等。 。
乔恩斯基特

100

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

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

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

LoadNextItem返回一个Task,该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;
        }
    }
}

现在好多了,不是吗?


谢谢,非常好的解释
Elger Mensonides 2014年

这是一个很好的例子
Royi Namir
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.