C# ConfigureAwait(false)在UI程序中避免了死锁

ConfigureAwait(false):当 await 等待完成时,它会尝试在线程池上下文中执行 await 之后的代码

ConfigureAwait(true):当 async 方法内的 await 执行完成时,它会尝试获取之前调用者线程所在的上下文执行方法的剩余部分

在Winform程序中没有使用ConfigureAwait(false)导致的死锁示例:

新建一个winform桌面程序,页面如下

 

 在button1的单击处理事件代码如下:

 1         private void button1_Click(object sender, EventArgs e)
 2         {
 3             MessageBox.Show($"开始运行,ThreadId:{Thread.CurrentThread.ManagedThreadId}", "button1_Click");
 4             Test();
 5             MessageBox.Show($"运行结束,ThreadId:{Thread.CurrentThread.ManagedThreadId}", "button1_Click");
 6         }
 7 
 8         public static void Test()
 9         {
10             var task = XXXAsync(); // 开启异步任务,比如下载某个资源
11             var resuslt = task.Result; // 获取异步任务的结果显示给用户,这里会阻塞当前ui线程直到异步线程返回结果
12             MessageBox.Show($"XXXAsync运行结束显示异步任务结果:{resuslt},ThreadId:{Thread.CurrentThread.ManagedThreadId}", "Test");
13         }
14 
15         private static async Task<string> XXXAsync()
16         {
17             await Task.Delay(1000); // 这里会导致死锁
18             //await Task.Delay(1000).ConfigureAwait(false); // 这样就不会死锁
19 
20             MessageBox.Show($"XXXAsync运行结束,ThreadId:{Thread.CurrentThread.ManagedThreadId}", "XXXAsync");
21 
22             return "XXXAsync.Result";
23         }

运行程序:

 

 发生死锁后,无法通过关闭按钮关闭程序了;

死锁分析如下:

默认情况下,当 Wait() 未完成的 Task 时,会捕获当前线程(此时是ui主线程)上下文,在 Task(即Task.Delay(1000)) 完成时使用该ui主线程上下文恢复方法的执行。 当 async 方法内的 await 执行完成时,它会尝试获取调用者线程(这时是ui主线程)所在的上下文执行方法的剩余部分, 但是该上下文已含有一个线程(即Test方法里的task.Result等待获取异步任务结果),该线程在等待 async 方法完成。然后它们相互等待对方,然后就没有然后了,死在那里

解决方法1:使用ConfigureAwait(false)让await后面的代码尝试在线程池上下文中执行,不在ui线程的同步上下文中执行

运行结果:

 

 解决方法2:ui线程里不去获取异步任务的结果

 

 运行结果:

 

posted @ 2021-01-02 14:31  温故纳新  阅读(1066)  评论(0编辑  收藏  举报