我知道等待任务的异步将执行返回给调用方,使其继续执行直到需要结果为止。
在某种程度上,我对我的想法的解释是正确的。看起来好像发生了某种交错。我希望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应用程序来测试我的代码。
这是一个WPF应用程序,所有这些代码都在同一UI线程上执行。await
您代码中的每个延续都通过进行安排DispatcherSynchronizationContext.Post
,该过程将特殊的Windows消息发布到UI线程的消息队列中。每个延续都按其消息发布的顺序发生(这是特定于实现的,您不应该依赖于此,但这就是这里的工作方式)。
因此,的延续End Do3: Foo
实际上是在的之后End Do3: Bar
。输出正确。
现在,更多细节。当我询问WinForms vs WPF时,我希望您的“预期”输出与实际输出匹配。我刚刚在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之间的差异,尽管两者都运行消息循环,而我们在这里只处理单线程代码?答案可以在这里找到:
为什么每个Dispatcher.BeginInvoke回调都具有唯一的同步上下文?
WPFDispatcherSynchronizationContext.Post
只是调用Dispatcher.BeginInvoke
,而WPF的一个重要实现细节是每个Dispatcher.BeginInvoke
回调都在其自己的唯一同步上下文上执行,如链接的问题所述。
这会影响对象(如)的await
延续。在WinForms中,这样的连续是内联的(同步执行),因为保持不变。在WPF中,他们没有内联,但通过比较贴,因为之前和完成后是不一样的(它被里面比较由运行时基础结构代码)。Task
await doFunc()
SynchronizationContext.Current
SynchronizationContext.Post
SynchronizationContext.Current
await task
task
task.GetAwaiter().OnCompleted
await
因此,在WPF中,它通常是相同的UI线程,但是与此线程关联的同步上下文不同,因此,继续操作可能会导致另一个异步PostMessage
回调被消息循环发布,泵送和执行。除了YieldAwaitable
(由返回Task.Yield
),您还会遇到TaskCompletionSource.SetResult
WPF UI线程触发的-style延续的这种行为。
这是相当复杂但特定于实现的东西。如果要精确控制异步await
继续的顺序,则可能需要推出自己的同步上下文,类似于Stephen Toub的AsyncPump
。尽管通常不需要,尤其是对于UI线程。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句