WinForms执行同步方法,在不阻止UI的情况下调用异步方法

时间:2016-09-29 23:11:14

标签: c# wpf multithreading winforms

所以我试图让一个驱动程序在WinForms应用程序中工作(我希望我可以去WPF,但这不是一个选项)并且我遇到了一个有趣的问题。在驱动程序中调用await方法时,UI线程将永久阻塞。这是我的问题的一个非常简化的版本,这似乎只发生在Windows窗体应用程序上。

private void button_Click(object sender, EventArgs e)
{
    MessageBox.Show(this.TestSynchronous().ToString());
}

// I'm trying to avoid having to change the code below
private int TestSynchronous()
{
    return this.TestAsync().Result;
}

private async Task<int> TestAsync()
{
    await Task.Delay(100);

    var rand = new Random();

    return rand.Next();
}

有没有办法可以将呼叫包裹到&#39; TestSynchronous&#39;防止它永久阻塞UI线程的方法?请记住,我试图避免不得不改变“测试”。任何方式的方法。

我看过像await works but calling task.Result hangs/deadlocks这样的重复类似帖子,这些帖子与我的问题类似,但我的问题无法以同样的方式解决。原因是他可以直接访问等待的异步方法,而我却没有。重申一下,我打电话给一个只向我提供“测试同步”的图书馆。方法,我想知道是否有任何方法可以包装方法,因此它不会阻塞某个地方进入库,我无法访问更改代码。

2 个答案:

答案 0 :(得分:7)

  

在驱动程序中调用await方法时,UI线程会永远阻塞。

您正在我的博客上遇到我完整描述的common deadlock problem。简而言之,await捕获&#34;背景&#34;并使用它来恢复其async方法。 WinForms有一个SynchronizationContext在UI线程上恢复。当您通过调用Result 阻止 UI线程时,async方法无法恢复,因此存在死锁。

  

有没有办法可以将呼叫包裹到&#39; TestSynchronous&#39;防止它永久阻塞UI线程的方法?

无解决方案 适用于所有情况。

最佳方法是允许async在代码库中自然增长:

private async Task<int> TestNoLongerSynchronousAsync()
{
  return await this.TestAsync();
}

private async void button_Click(object sender, EventArgs e)
{
  MessageBox.Show((await this.TestNoLongerSynchronousAsync()).ToString());
}

无论你认为这有多难,它几乎肯定比做同步异步黑客更容易。

如果您无法使用await(对于非常强大的定义&#34;可以&#39;#&#34;) ,然后你可以使用我brownfield async article中描述的其中一个黑客:

  • 阻止(已经尝试过,没有工作)。
  • 阻塞线程池线程(即Task.Run(() => this.TestAsync()).Result)。
  • 抽取嵌套的消息循环(最危险的)。

这些都不适用于所有情况。在某些情况下,没有解决方案。祝你好运。

答案 1 :(得分:0)

所以我实际上能够解决这个问题,答案其实很简单。以下是该示例的修改版本。

private void button_Click(object sender, EventArgs e)
{
    var task = Task.Run<int>(
            () => this.TestSynchronous());

    task.Wait();

    MessageBox.Show(task.Result.ToString());
}

还有其他方法可以有效地做同样的事情,但这似乎适用于我的问题。有趣的是我之前(在发布我的问题之前)尝试使用类似的方法修复它并且它不起作用但它现在确实如此,所以我不知道我做错了什么。