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

时间:2013-02-11 20:50:33

标签: c# wpf winforms task-parallel-library

我们有一个供WPF和/或Winforms客户使用的库。

我们提供了类似于:

的异步方法
Task<int> GetIntAsync()

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

int GetInt();

基本上只调用异步方法并在其任务上调用.Result

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

所以问题是当在主线程上调用GetInt()时,它会死锁,因为

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

已经消耗了同步方法,因此删除它会是一个重大变化。因此,我们选择在同步GetInt()方法中使用WaitWithPumping来允许调用主线程。

除了从其UI代码中使用GetInt()的客户端之外,此方法正常。以前,他们预计使用GetInt()会使他们的UI无响应 - 也就是说,如果他们从按钮的click事件处理程序中调用GetInt(),他们会希望在处理程序返回之前不会处理任何Windows消息。现在消息被抽取,他们的UI 响应,并且可以再次点击相同的按钮(并且他们可能没有将其处理程序编码为可重入)。

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

问题:

  • 有没有办法做一个WaitWithPumping来抽取“调用主要”消息,但不能提取其他与UI相关的消息?
  • 如果客户端UI的行为就像当前显示的模式对话框一样,虽然是隐藏的(即用户无法访问其他窗口),但这样就足够了。但是,根据我的阅读,你无法隐藏模态对话框。
  • 您可以想到的其他解决方法将不胜感激。

1 个答案:

答案 0 :(得分:4)

您可以在GetInt的上下文中创建自己的消息泵,而不是使用现有的消息泵。 Here是一篇博客文章,讨论如何撰写一篇文章。 This is the full solution the blog creates

使用它你可以写成:

public int GetInt()
{
    return AsyncPump.Run(() => GetIntAsync());
}

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