通过异步同步避免死锁并防止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线程上运行。