C# 等待与继续的区别

C# 等待与继续的区别,c#,task-parallel-library,task,async-await,C#,Task Parallel Library,Task,Async Await,有人能解释一下在下面的例子中,wait和ContinueWith是否同义吗。我第一次尝试使用TPL,并且已经阅读了所有文档,但不理解其中的区别 等待: String webText = await getWebPage(uri); await parseData(webText); Task<String> webText = new Task<String>(() => getWebPage(uri)); Task continue = webText.Cont

有人能解释一下在下面的例子中,
wait
ContinueWith
是否同义吗。我第一次尝试使用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();
继续进行

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();
taskwebtext=newtask(()=>getWebPage(uri));
Task continue=webText.ContinueWith((Task)=>parseData(Task.Result));
webText.Start();
继续。等待();

在特定的情况下,一种方法比另一种方法更可取吗?

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

它们非常相似,都安排了一个延续,但是一旦控制流变得稍微复杂,
wait
就会产生更简单的代码。此外,正如Servy在评论中指出的,等待任务将“展开”聚合异常,这通常会导致更简单的错误处理。另外,使用
await
将在调用上下文中隐式安排继续(除非使用
ConfigureAwait
)。没有什么是不能“手动”完成的,但是使用
wait
更容易完成


我建议您尝试使用
await
任务来实现稍微大一点的操作序列。继续执行
——这会让您大开眼界。

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

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

while (true) {
    string result = LoadNextItem().Result;
    if (result.Contains("target")) {
        Counter.Value = result.Length;
        break;
    }
}
LoadNextItem
返回一个任务,该任务最终将产生一些您想要检查的结果。如果当前结果是您要查找的结果,则更新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);
        }});
}

现在好多了,不是吗?

如果在第二个示例中删除了
Wait
调用,那么这两个代码片段(大部分)是等效的。仅供参考:您的
getWebPage
方法不能在两个代码中都使用。在第一个代码中,它具有
任务
返回类型,而在第二个代码中,它具有
字符串
返回类型。所以基本上你的代码不能编译如果准确地说,“在特定情况下,一个优先于另一个吗?”在添加async\await之前继续存在。async\await出现后,ContinueWith可能已被标记为过时,但由于某些原因它不是。两个代码段之间的错误处理也不同;在这方面,使用
await
比使用
ContinueWith
更容易。@Servy:True,将在这一点上添加一些内容。调度也非常不同,即,在什么上下文中执行
parseData
。当您说使用await时,将在调用上下文中隐式地调度continuence,你能解释一下这样做的好处以及在另一种情况下会发生什么吗?@Harrison:想象一下你正在编写一个WinForms应用程序——如果你编写一个异步方法,默认情况下,该方法中的所有代码都将在UI线程中运行,因为延续将安排在那里。如果不指定要在何处运行延续,我不知道默认值是什么,但它很容易在线程池线程上运行。。。在这一点上,你不能访问UI,等等。谢谢,非常好的解释这是一个很好的例子