我理解等待任务的异步会将执行返回给调用者,允许它继续执行直到需要结果。
我对我的想法的解释是正确的,直到某一点。看起来似乎正在进行某种交错。我希望Do3()完成,然后将调用堆栈备份到Do2()。查看结果。
await this.Go();
哪个电话
async Task Go()
{
await Do1(async () => await Do2("Foo"));
Debug.WriteLine("Completed async work");
}
async Task Do1(Func<Task> doFunc)
{
Debug.WriteLine("Start Do1");
var t = Do2("Bar");
await doFunc();
await t;
}
async Task Do2(string id)
{
Debug.WriteLine("Start Do2: " + id);
await Task.Yield();
await Do3(id);
Debug.WriteLine("End Do2: " + id);
}
async Task Do3(string id)
{
Debug.WriteLine("Start Do3: " + id);
await Task.Yield();
Debug.WriteLine("End Do3: " + id); // I did not expect Do2 to execute here once the method call for Do3() ended
}
预期结果:
// Start Do1
// Start Do2: Bar
// Start Do2: Foo
// Start Do3: Bar
// Start Do3: Foo
// End Do3: Bar
// End Do2: Bar
// End Do3: Foo
// End Do2: Foo
//Completed async work
实际输出:
//Start Do1
//Start Do2: Bar
//Start Do2: Foo
//Start Do3: Bar
//Start Do3: Foo
//End Do3: Bar
//End Do3: Foo
//End Do2: Bar
//End Do2: Foo
//Completed async work
这里到底发生了什么?
我使用.NET 4.5和一个简单的WPF应用来测试我的代码。
答案 0 :(得分:2)
这是一个WPF应用程序,所有这些代码都在同一个UI线程上执行。代码中的每个await
延续都是通过DispatcherSynchronizationContext.Post
来安排的,它会将特殊的Windows消息发布到UI线程的消息队列中。每个延续按照其消息发布的顺序发生(这是特定于实现的,您不应该依赖于此,但这是如何在这里工作的。)
因此,End Do3: Foo
的延续确实发布在End Do3: Bar
的延续之后。输出是正确的。
现在,更多详细信息。当我询问WinForms vs WPF时,我期待你的预期&#34;输出以匹配实际输出。我刚刚在WinForms下测试它,它确实匹配:
// Start Do1
// Start Do2: Bar
// Start Do2: Foo
// Start Do3: Bar
// Start Do3: Foo
// End Do3: Bar
// End Do2: Bar
// End Do3: Foo
// End Do2: Foo
//Completed async work
那么,为什么WPF和WinForms之间的差异,虽然都运行消息循环,我们只处理单线程代码?答案可以在这里找到:
Why a unique synchronization context for each Dispatcher.BeginInvoke callback?
WPF&#39; DispatcherSynchronizationContext.Post
只调用Dispatcher.BeginInvoke
,WPF的一个重要实现细节是每个Dispatcher.BeginInvoke
回调都在其自己唯一的同步上下文上执行,如在相关问题中解释。
这会影响await
对象的Task
延续(例如await doFunc()
)。在WinForms中,这种延续是内联的(同步执行),因为SynchronizationContext.Current
保持不变。在WPF中,他们没有内联,而是通过SynchronizationContext.Post
发布,因为SynchronizationContext.Current
之前 await task
并且在完成task
之后不一样(它在task.GetAwaiter().OnCompleted
运行时基础结构代码中在await
内进行比较)。
因此,在WPF中,它通常是相同的UI线程,但与此线程关联的同步上下文不同,因此延续可能会导致另一个异步PostMessage
回调被发布,泵送和执行通过消息循环。除了YieldAwaitable
(由Task.Yield
返回)之外,您还会遇到从WPF UI线程触发的TaskCompletionSource.SetResult
样式延续的此行为。
这是相当复杂但特定于实现的东西。如果您想要精确控制异步await
延续的顺序,您可能想要推出自己的同步上下文,类似于Stephen Toub的AsyncPump
。虽然通常你不需要,特别是对于UI线程。
答案 1 :(得分:0)
结果并不让我感到惊讶。如果Do3结束,我不明白为什么它应该立即继续Do2。它是异步过程的所有部分,因此实际输出非常有意义。但是,如果不同的运行会产生不同的结果,我不会感到惊讶。
Do3:Bar和Do3:Foo是不同的任务,(可能)在不同的线程中同时执行。完成后,会通知各自的呼叫者,但该通知本身可能也是异步的。 await
没有让两个任务在同一个线程中神奇地运行,只是让第一个任务等到另一个完成。因此,当Do2:Bar正在等待该信号时,Do3:Foo可以继续运行并完成。