为什么在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() => 死锁。