WPF Dispatcher.InvokeAsync与异步委托的异常行为咨询
问题场景回顾
先看你给出的核心代码示例:
定义异步方法:
private async Task DoAsync() { Console.WriteLine(@"(1.1)"); Thread.Sleep(200); Console.WriteLine(@"(1.2)"); await Task.Delay(1000); Console.WriteLine(@"(1.3)"); }
使用Dispatcher.InvokeAsync调用的代码:
Console.WriteLine(@"(1)"); await Application.Current.Dispatcher.InvokeAsync(DoAsync); Console.WriteLine(@"(2)");
输出顺序为:
(1) (1.1) (1.2) (2) (1.3)
改用Dispatcher.BeginInvoke监听Completed事件的写法:
Console.WriteLine(@"(1)"); var dispatcherOp = Dispatcher.BeginInvoke(new Func<Task>(DoAsync)); dispatcherOp.Completed += (s, args) => { Console.WriteLine(@"(2)"); };
也会得到一致的输出顺序,核心现象是:DoAsync中的await会让Dispatcher认为当前调度操作已结束。
为什么这是特性?
这是WPF Dispatcher的设计行为,核心逻辑和它对异步委托的处理方式有关:
针对
Dispatcher.InvokeAsync的逻辑
当你传入返回Task的异步委托时,InvokeAsync实际返回的是Task<Task>——外层的Task仅代表「Dispatcher成功调度并执行到异步委托的第一个await挂起点」这个操作完成,而非整个DoAsync方法的异步流程完成。
你代码里的await InvokeAsync(DoAsync)只等待了外层Task结束,所以当DoAsync执行到await Task.Delay(1000)挂起时,外层Task就完成了,代码会继续执行Console.WriteLine((2));而DoAsync剩下的延续逻辑(输出(1.3))会在后续的Dispatcher循环里执行,不会被这个外层await跟踪。针对
Dispatcher.BeginInvoke的逻辑
这是基于旧APM(异步编程模型)设计的API,原本是为同步委托服务的。当你传入异步委托时,Dispatcher只会执行到委托的第一个await挂起点——因为异步方法在挂起时会立即返回一个Task,而BeginInvoke不会跟踪这个返回的Task,它会直接认为委托已经执行完毕,因此立刻触发Completed事件。
相关文档说明
官方文档明确了这个行为:
Dispatcher.InvokeAsync的文档指出:当传入异步委托时,返回的Task仅表示调度操作的完成,而非异步委托本身的完成。若要等待整个异步方法完成,需要等待返回的嵌套Task(即await await InvokeAsync(DoAsync))。Dispatcher.BeginInvoke的文档提到:该API不支持跟踪异步委托的后续异步操作,仅适用于同步逻辑的调度。
内容的提问来源于stack exchange,提问作者Maciek Świszczowski

