等待和继续之间的区别

本文关键字:区别 之间 继续 等待 | 更新日期: 2023-09-27 18:32:31

有人可以解释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();

在特定情况下,一个是否优于另一个?

等待和继续之间的区别

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

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

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

LoadNextItem返回一个任务,最终将产生一些您想要检查的结果。如果当前结果是您要查找的结果,则更新 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;
        }
    }
}

现在好多了,不是吗?

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

它们非常相似,因为它们都计划延续,但是一旦控制流变得稍微复杂,await就会产生更简单的代码。此外,正如 Servy 在评论中指出的那样,等待任务将"解包"聚合异常,这通常会导致更简单的错误处理。此外,使用 await 将在调用上下文中隐式调度延续(除非您使用 ConfigureAwait (。没有什么是不能"手动"完成的,但是使用await要容易得多。

我建议您尝试使用 awaitTask.ContinueWith 实现稍大的操作序列 - 这可能会让您大开眼界。