为什么在WPF中使用ManualResetEvent和异步方法会出现死锁?
Why does deadlock occur when using ManualResetEvent and asynchronous method in WPF?
这是我在 WPF.NET 中遇到的问题。为了说明问题,让我们看看下面的 class :
public class TaskRunnerWithProgressFeedback(){
ManualResetEvent _event = new ManualResetEvent(false);
public void RunTask(Action action) {
_event.Reset();
//Display loading screen
RunAsync(action);
Console.WriteLine("Load completed.");
//Hide loading screen
_event.WaitOne();
}
private async void RunAsync(Action action) {
await Task.Run(() => action.Invoke());
_event.Set();
}
}
所以我这里有这个 class,我将从 UI 线程调用 RunTask
方法。
例如:
private void Button1_OnClick(object sender , RoutedEventArgs e) {
var x = new TaskRunnerWithProgressFeedback();
x.RunTask(()=>{ /*Some time-consuming action*/ });
}
而当Button1被点击时,整个程序就陷入了僵局。你对这种情况有什么解释吗?
脚注:我需要 TaskRunnerWithProgressFeedback
class 来进行行为测试。我没有使用 BackgroundWorker
因为它会破坏那些测试。
一旦您在 RunAsync
方法中创建的 Task
完成,对 _event.Set()
的调用应该在 UI 线程上执行。
如果您在 UI 线程上调用 _event.WaitOne()
任务完成之前,就会发生死锁。
因为 UI 线程等待 ManualResetEvent
被设置,但它永远不会是因为调用 Set()
方法的代码无法在 UI 线程,因为它被 WaitOne()
调用阻塞。
这基本上就是 async
/await
的工作原理。 async
方法同步运行,直到它命中 await
,然后 return 到调用者。上下文(在本例中为调度程序线程)被捕获,然后 async
方法的其余部分在等待的 async
方法被调用的同一 dispatcher/UI 线程上执行方法已经完成。
但是在上下文线程空闲之前,当然不能执行该方法的其余部分。在这种情况下,它永远不会是因为它等待 async
方法的其余部分调用 Set()
=> 死锁。
这是我在 WPF.NET 中遇到的问题。为了说明问题,让我们看看下面的 class :
public class TaskRunnerWithProgressFeedback(){
ManualResetEvent _event = new ManualResetEvent(false);
public void RunTask(Action action) {
_event.Reset();
//Display loading screen
RunAsync(action);
Console.WriteLine("Load completed.");
//Hide loading screen
_event.WaitOne();
}
private async void RunAsync(Action action) {
await Task.Run(() => action.Invoke());
_event.Set();
}
}
所以我这里有这个 class,我将从 UI 线程调用 RunTask
方法。
例如:
private void Button1_OnClick(object sender , RoutedEventArgs e) {
var x = new TaskRunnerWithProgressFeedback();
x.RunTask(()=>{ /*Some time-consuming action*/ });
}
而当Button1被点击时,整个程序就陷入了僵局。你对这种情况有什么解释吗?
脚注:我需要 TaskRunnerWithProgressFeedback
class 来进行行为测试。我没有使用 BackgroundWorker
因为它会破坏那些测试。
一旦您在 RunAsync
方法中创建的 Task
完成,对 _event.Set()
的调用应该在 UI 线程上执行。
如果您在 UI 线程上调用 _event.WaitOne()
任务完成之前,就会发生死锁。
因为 UI 线程等待 ManualResetEvent
被设置,但它永远不会是因为调用 Set()
方法的代码无法在 UI 线程,因为它被 WaitOne()
调用阻塞。
这基本上就是 async
/await
的工作原理。 async
方法同步运行,直到它命中 await
,然后 return 到调用者。上下文(在本例中为调度程序线程)被捕获,然后 async
方法的其余部分在等待的 async
方法被调用的同一 dispatcher/UI 线程上执行方法已经完成。
但是在上下文线程空闲之前,当然不能执行该方法的其余部分。在这种情况下,它永远不会是因为它等待 async
方法的其余部分调用 Set()
=> 死锁。