ConfigureAwait(false) 如何防止 Ui 死锁

use*_*079 2 asp.net multithreading task winforms task-parallel-library

我知道这个问题已经被无休止地问了,但仍然如此。我想我错过了一些东西。当我们想要以异步方式更新 UI(为了论证而使用 WinForm UI)时。但是我们不能使用 async /await 关键字,而必须使用ConfigureAwait(false)。据我了解,它“告诉”任务它可以在任何可用线程上自行恢复,而不是等待主线程。它通过释放 UI 来防止死锁,因为它不等待长进程完成,并且任务不等待主 UI 线程可用。

下面的代码演示了经典的死锁

public void Button_Click()
{

    SomeTextBox.Text =LongProcessAsync().Result;
}
Run Code Online (Sandbox Code Playgroud)

现在我的问题开始了:)。长进程任务完成后,UI 线程最终如何更新 UI。

是因为任务将结果传递给另一个 UI 进程来完成作业吗?UI 队列消息如何与该部分相关?当说只有一个线程更新 ui 时,是否意味着该线程在应用程序的整个生命周期中都存在,或者只有一个线程正在更新 ui,但可以创建一个新线程并执行该操作?

谢谢

Rez*_*aei 10

我建议阅读 Stephen Cleary 关于此主题的博客文章:不要阻止异步代码,其中解释了死锁如何发生以及如何避免它。

注意“死锁”和“UI 阻塞”的区别。当两个线程互相等待时会发生死锁,当 UI 线程繁忙/阻塞并且无法处理 UI 消息时会发生阻塞 UI。这里使用ConfigureAwait(false)并不能防止“UI 阻塞”,但可以防止“死锁”。

当您编写运行任务的异步库方法时,为了防止可能的死锁,建议通过ConfigureAwait(false). 这是为了防止死锁,即使库的用户通过调用Result或获得异步方法的结果也是如此Wait

ConfigureAwait(false)基本上告诉:在该行之后不要返回到原始上下文并继续在线程池线程上执行。

为了更好地理解它,请看这个例子:

// UI code 
1:  private void button1_Click(object sender, EventArgs e) 
2:  { 
3:      var result = GetDataAsync().Result; 
4:      MessageBox.Show(result); 
5:  } 

// A library code
6:  public async Task<string> GetDataAsync() 
7:  { 
8:      await Task.Delay(1000); 
9:      return "Data"; 
10: } 
Run Code Online (Sandbox Code Playgroud)

考虑到以下事实:

  • 在 Windows 窗体中,所有 UI 代码都在单个线程中执行;用户界面线程。

  • Result方法会Task阻塞调用线程,直到任务结果准备就绪。

这就是这里发生的情况:

  • 在第 3 行,GetDataAsync将在 UI 线程中调用并执行到第 8 行。
  • 在第 8 行,将创建一个任务并将在线程池线程上运行,await 告诉运行该任务后,应在先前捕获的上下文(即 UI 线程上下文)中继续执行。
  • 未完成的任务返回到原始调用者(在第 3 行),该调用者将在将来完成(完成第 8 行任务,然后运行第 9 行)。
  • 然后Result将执行第 3 行的方法,该方法会阻塞任务,直到任务完成,最终结果为GetDataAsync准备就绪。
  • 当第 8 行等待的任务完成时,调度程序尝试在 UI 线程中运行第 9 行,但 UI 线程被阻塞!所以第9行无法执行,也GetDataAsync无法完成。

发生死锁:UI 线程正在等待GetDataAsync完成,并GetDataAsync正在等待主线程空闲以执行其余代码。

为了避免死锁,您应该使用 ,而不是通过Waitor获取异步方法的结果。但正如我之前提到的,在上面的场景中,如果您作为库开发人员运行您的任务(此处),它可以防止死锁,因为它告诉您不要在该行之后返回到原始上下文并继续在线程池线程上执行。因此,在第 8 行,它告诉在线程池线程中继续,因此虽然 UI 线程被阻塞,但第 9 行执行并向 UI 线程返回数据并解除阻塞。ResultawaitTask.DelayConfigureAwait(false)

// UI code 
1:  private void button1_Click(object sender, EventArgs e) 
2:  { 
3:      var result = GetDataAsync().Result; 
4:      MessageBox.Show(result); 
5:  } 

// A library code
6:  public async Task<string> GetDataAsync() 
7:  { 
8:      await Task.Delay(1000).ConfigureAwait(false); 
9:      return "Data"; 
10: } 
Run Code Online (Sandbox Code Playgroud)

但再次请记住,作为 UI 开发人员,您应该始终使用awaitwaiting Task,而不应该使用Resultor Wait