我们有一个供 WPF 和/或 Winforms 客户端使用的库。
我们提供了类似于以下的异步方法:
Task<int> GetIntAsync()
我们还(不幸的是)提供了一个同步包装器方法:
int GetInt();
本质上只是调用异步方法并在其任务上调用 .Result
。
我们最近意识到,在某些情况下,GetIntAsync
中的某些代码需要在主 UI 线程上运行(它需要使用标记为“单”线程模型的旧版 COM 组件(即组件必须在主 STA 线程中运行,而不仅仅是任何 STA 线程)
所以问题是当在主线程上调用GetInt()
时,它会死锁因为
.Result
阻塞主线程,GetIntAsync()
中的代码使用Dispatcher.Invoke
尝试在主线程上运行。
同步方法已经被使用,所以删除它是一个破坏性的改变。因此,我们选择使用 WaitWithPumping在我们的同步 GetInt()
方法中允许调用主线程。
除了从其 UI 代码使用 GetInt()
的客户端外,这工作正常。以前,他们预计使用 GetInt()
会使他们的 UI 无响应——也就是说,如果他们从按钮的点击事件处理程序中调用 GetInt()
,他们会期望在处理程序返回之前没有处理任何 Windows 消息。现在消息已被发送,他们的 UI 是响应式的并且可以再次点击同一个按钮(他们可能没有将他们的处理程序编码为可重入)。
如果有一个合理的解决方案,我们希望我们的客户不需要在调用 GetInt
期间针对响应的 UI 进行编码
问题:
- 有没有一种方法可以执行
WaitWithPumping
以发送“Invoke to main”消息,但不发送其他 UI 相关消息? - 如果客户端 UI 的行为就像当前显示的模态对话框一样,尽管是隐藏的(即用户无法访问其他窗口),但对我们的目的来说就足够了。但是,据我了解,您无法隐藏模态对话框。
- 您能想到的其他解决方法将不胜感激。
最佳答案
您可以在 GetInt
的上下文中创建自己的消息泵,而不是利用现有的消息泵。 Here是一篇讨论如何撰写的博客文章。 This is the full solution the blog creates .
使用它你可以把它写成:
public int GetInt()
{
return AsyncPump.Run(() => GetIntAsync());
}
这将导致按预期完全阻塞 UI 线程,同时仍然确保从 GetIntAsync
调用的所有延续不会死锁,因为它们将被编码到不同的 SynchronizationContext
。另请注意,此消息泵仍在主 STA/UI 线程上运行。
关于c# - 通过异步同步避免死锁并防止 UI 响应,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14820606/