通过异步同步避免死锁并防止UI响应
我们有一个由WPF和/或Winforms客户端使用的库。
我们提供了一个类似于的异步方法:
Task GetIntAsync()
我们(不幸的是)还提供了一个同步包装器方法:
int GetInt();
它基本上只是调用异步方法并调用.Result
来完成它的任务。
我们最近意识到在某些情况下GetIntAsync
一些代码需要在主UI线程上运行(它需要使用标记为“单个”线程模型的传统COM组件(即组件必须在主STA线程中运行而不仅仅是任何STA线程)
所以问题是当在主线程上调用GetInt()
时,它将会死锁
-
.Result
阻塞主线程, -
GetIntAsync()
的代码使用Dispatcher.Invoke
尝试在主线程上运行。
同步方法已被消耗,因此删除它将是一个重大变化。 因此,我们选择在同步GetInt()
方法中使用WaitWithPumping来允许调用主线程。
除了从UI代码中使用GetInt()
客户端之外,这种方法GetInt()
。 以前,他们预计使用GetInt()
会使UI无法响应 – 也就是说,如果他们从按钮的click事件处理程序中调用GetInt()
,他们会期望在处理程序返回之前不会处理任何Windows消息。 现在消息被抽取,他们的UI响应,并且可以再次点击相同的按钮(并且他们可能不会将其处理程序编码为可重入)。
如果有合理的解决方案,我们希望我们的客户不需要在调用GetInt
期间针对响应的UI进行GetInt
题:
- 有没有办法做一个
WaitWithPumping
,它将泵出“调用主要”消息,但不会WaitWithPumping
其他与UI相关的消息? - 如果客户端UI的行为就像当前显示模式对话框一样,虽然是隐藏的(即用户无法访问其他窗口),但这足以满足我们的目的。 但是,从我读到的内容来看,你无法隐藏modal dialog。
- 您可以想到的其他解决方法将不胜感激。
您可以在GetInt
的上下文中创建自己的消息泵,而不是使用现有的消息泵。 这是一篇讨论如何撰写的博客文章。 这是博客创建的完整解决方案 。
使用它你可以写成:
public int GetInt() { return AsyncPump.Run(() => GetIntAsync()); }
这将导致按预期完全阻止UI线程,同时仍然确保从GetIntAsync
调用的所有连续都不会死锁,因为它们将被封送到不同的SynchronizationContext
。 另请注意,此消息泵仍在主STA / UI线程上运行。