通过异步同步避免死锁并防止UI响应

Mat*_*ith 6 c# wpf winforms task-parallel-library

我们有一个由WPF和/或Winforms客户端使用的库.

我们提供了一个类似于的异步方法:

Task<int> GetIntAsync()
Run Code Online (Sandbox Code Playgroud)

我们(不幸的是)还提供了一个同步包装器方法:

int GetInt();
Run Code Online (Sandbox Code Playgroud)

它本质上只是调用异步方法并调用.Result其任务.

我们最近在某些情况下意识到GetIntAsync需要在主UI线程上运行一些代码(它需要使用标记为"单个"线程模型的传统COM组件(即组件必须在主STA线程中运行而不是任何STA线程)

所以问题是,当GetInt()被称为主线程,它会死锁,因为

  • .Result块为主线,
  • 中的代码GetIntAsync()使用Dispatcher.Invoke尝试在主线程上运行.

同步方法已被消耗,因此删除它将是一个重大变化.因此,我们选择在同步方法中使用WaitWithPumpingGetInt()来允许调用主线程.

除了使用GetInt()UI代码的客户端之外,这种方法很好.以前,他们预计使用GetInt()会使他们的UI无法响应 - 也就是说,如果他们GetInt()从按钮的click事件处理程序中调用,他们会期望在处理程序返回之前不会处理任何Windows消息.现在,消息被泵送,其UI 响应和相同的按钮可以再次点击(他们可能没有编写自己的处理程序是重入).

如果有一个合理的解决方案,我们希望我们的客户不需要在调用期间针对响应的UI进行编码 GetInt

题:

  • 有没有办法做一个WaitWithPumping将"调用主要"消息,但不泵送其他UI相关的消息?
  • 如果客户端UI的行为就像当前显示模式对话框一样,虽然是隐藏的(即用户无法访问其他窗口),但这足以满足我们的目的.但是,从我读到的内容来看,你无法隐藏模态对话框.
  • 您可以想到的其他解决方法将不胜感激.

Ser*_*rvy 4

您可以在 的上下文中创建自己的消息泵,而不是利用现有的消息泵GetInt是一篇讨论如何编写的博客文章。 这是博客创建的完整解决方案

使用它你可以将其写为:

public int GetInt()
{
    return AsyncPump.Run(() => GetIntAsync());
}
Run Code Online (Sandbox Code Playgroud)

这将导致按预期完全阻塞 UI 线程,同时仍然确保所有调用的延续GetIntAsync不会死锁,因为它们将被编组到不同的SynchronizationContext. 另请注意,此消息泵仍在主 STA/UI 线程上运行。