Wong Jia Hau
Wong Jia Hau

Reputation: 3069

Why does deadlock occur when using ManualResetEvent and asynchronous method in WPF?

This is a question I faced in WPF.NET. To illustrate the problem, let us take a look at the class below :

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();
    }
} 

So I have this class here, and I will call the RunTask method from a UI thread. For example :

private void Button1_OnClick(object sender , RoutedEventArgs e) {
    var x = new TaskRunnerWithProgressFeedback();
    x.RunTask(()=>{ /*Some time-consuming action*/ });
}

And when Button1 is clicked, the whole program runs into a deadlocked situation. Do you have any explanation for this situation?

Foot note : I need the TaskRunnerWithProgressFeedback class for me to do behavioral testing. I'm not using BackgroundWorker because it will break those tests.

Upvotes: 1

Views: 417

Answers (1)

mm8
mm8

Reputation: 169270

The call to _event.Set() is supposed to be executed on the UI thread once the Task that you create in the RunAsync method has completed.

The deadlock occurs if you call _event.WaitOne() on the UI thread before that task has completed.

Because then the UI threads waits for the ManualResetEvent to be set but it never will be because the code that calls the Set() method cannot be executed on the UI thread because it is blocked by the WaitOne() call.

This is basically how async/await works. The async method runs synchronously until it hits and await, and then return to the caller. The context (the dispatcher thread in this case) is captured and the remainder of the async method is then executed back on the same dispatcher/UI thread that the async method was invoked on once the awaited method has completed.

But the remainder of the method can of course not be executed until the context thread is free. And in this case it never will be because it waits for the remainder of the async method to call Set() => Deadlock.

Upvotes: 3

Related Questions