Reputation: 3069
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
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