C# 死锁 Task/AutoResetEvent
原创
©著作权归作者所有:来自51CTO博客作者唐宋元明清2188的原创作品,请联系作者获取转载授权,否则将追究法律责任
与之前《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 async2 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原则
参考资料:
同步转异步
异步转同步
死锁
作者:唐宋元明清2188