在Dispatcher.Invoke()中调用异步方法是否会引发问题?返回值该如何设置?
问题分析与解答
代码是否会引发死锁或正常运行?
先看给出的代码:
void CalledFromAnotherThread() { Dispatcher.CurrentDispatcher.Invoke(DoUiStuff) } async Task DoUiStuff() { await DoAsyncStuff(); }
核心问题在于Dispatcher.CurrentDispatcher的行为,分两种情况:
- 绝大多数场景(非UI线程无消息循环):会引发死锁
非UI线程默认没有关联的Dispatcher,CurrentDispatcher会为该线程创建一个全新的Dispatcher实例,但这个Dispatcher并没有启动消息泵(Dispatcher.Run())。Invoke是同步调用,它会把DoUiStuff排入这个新Dispatcher的消息队列后阻塞等待执行,可没有消息循环就永远处理不了队列任务,最终导致调用线程永久卡住。 - 特殊场景(非UI线程已启动消息循环):能正常运行
如果该非UI线程手动启动了Dispatcher消息循环,Invoke会触发DoUiStuff执行,await DoAsyncStuff()会捕获当前Dispatcher上下文,异步操作完成后回到该线程继续执行后续逻辑。但这种场景非常少见,一般只有UI线程默认自带消息循环。
另外注意:这段代码里的DoUiStuff名字带"Ui",但实际不会运行在UI线程——CurrentDispatcher取的是调用线程的Dispatcher,而非UI线程的。如果你的本意是操作UI,应该用对应框架的主线程Dispatcher(比如WPF的Application.Current.Dispatcher)。
完全不等待DoUiStuff,是否应该将返回值改为void?
不应该,除非DoUiStuff是作为事件处理程序使用。
原因如下:
- 返回
Task时,方法内部的未捕获异常会被封装到返回的Task中,后续你仍可通过Task.Exception等方式获取并处理异常; - 若改成返回
void,方法内部的未捕获异常会直接抛到调用线程的同步上下文,大概率导致程序崩溃,且没有任何手段捕获该异常。
哪怕你完全不打算等待异步方法完成,也应该让它返回Task而非void。
内容的提问来源于stack exchange,提问作者codymanix
相关产品推荐
相关产品推荐

