C# 死锁 Task/AutoResetEvent

与之前《C# 死锁 TaskCompletionSource》类似,还有很多死锁的案例

使用Task异步转同步时,使用不当造成的死锁

 1     private void Task_OnClick(object sender, RoutedEventArgs e)
 2     {
 3         AwaitUsingTask(TestAsync());
 4         Debug.WriteLine("Task_OnClick end");
 5     }
 6     private void AwaitUsingTask(Task task)
 7     {
 8         task.Wait();
 9         //task.Result;
10     }

TestAsync:

1     private static async Task TestAsync()
2     {
3         Debug.WriteLine("异步任务start……");
4         await Task.Delay(2000);
5         Debug.WriteLine("异步任务end……");
6     }

 

使用AutoResetEvent不当,造成的死锁

 1     private void AwaitAutoResetEvent_OnClick(object sender, RoutedEventArgs e)
 2     {
 3         AwaitUsingAutoResetEvent(TestAsync());
 4         Debug.WriteLine("AwaitAutoResetEvent_OnClick end");
 5     }
 6 
 7     public void AwaitUsingAutoResetEvent(Task task)
 8     {
 9         AutoResetEvent autoResetEvent = new AutoResetEvent(false);
10 
11         task.ContinueWith(t =>
12         {
13             autoResetEvent.Set();
14         });
15         autoResetEvent.WaitOne();
16     }

以上死锁,是因为autoResetEvent.WaitOne(),锁住当前UI线程直到收到信号,但task是什么线程运行的?UI线程啊,所以task也被锁住了,所以就无法执行后面的逻辑autoResetEvent.Set()了。那会一直保持阻塞在那。

或者以终止状态=true为参数的AutoResetEvent,也是会死锁的,如下:

 1         private int index = 0;
 2         private async void AwaitAutoResetEvent_OnClick(object sender, RoutedEventArgs e)
 3         {
 4             await AwaitUsingAutoResetEvent(index++);
 5         }
 6         AutoResetEvent autoResetEvent = new AutoResetEvent(true);
 7         public async Task AwaitUsingAutoResetEvent(int ind)
 8         {
 9             autoResetEvent.WaitOne();
10             Debug.WriteLine($"Task.Delay(3000) start{ind}");
11             await Task.Delay(3000);
12             Debug.WriteLine($"Task.Delay(3000) end{ind}");
13             autoResetEvent.Set();
14         }

上面的案例,2次重入,就会暴露死锁的问题了(因为首次是终止的,第二次才会wait)。

那么如何解决?autoResetEvent.WaitOne();放在异步任务中等待即可

当然此方案会多产生一个task(有性能问题),详见AutoResetEvent的正确用法:C# 同步转异步 AutoResetEvent

 

以上死锁的原因:

  • 主执行线程调用子线程后挂起等待子线程结果
  • 子线程又需要切换到主线程或者等待主线程返回
  • 从而导致两个线程均处在阻塞状态(死锁)

如何避免:

如果已经使用了Async/Await,那尽量不要再使用Task.Wait()/Task.Result,让上下游的方法全部改为Async/Await原则

 

参考资料:

posted @ 2019-07-08 00:07  唐宋元明清2188  阅读(1424)  评论(0编辑  收藏  举报