I need an explanation why this code doesn't cause deadlock on the thread it's running in (It's a WinForm application and these happens in button_Click):
Task FakeMainThread = Task.Run(async() => // fake main thread
{
async Task asyncMethod() // executed in FakeMainThread
{
await Task.Run(() => // 'await'ing in FakeMainThread
{
Thread.Sleep(5000);
MessageBox.Show("child Task finished");
return 3;
});
MessageBox.Show("asyncMethod finished");
}
asyncMethod().Wait(); // deadlock should appear in FakeMainThread
MessageBox.Show("FakeMainThread finished");
return 4;
});
asyncMethod().Wait();
should block the fakeMainThread Task's thread while it's await another task to finish and since await
is happening in fakeMainThread too it shouldn't be able to 'await' and deadlock should appear on fakeMainThread. But it doesn't happen and I see bot MessageBox.Show("asyncMethod finished");
and MessageBox.Show("FakeMainThread finished");
messages.
Note: if I put this:
async Task asyncMethod()
{
await Task.Run(() =>
{
Thread.Sleep(5000);
MessageBox.Show("child Task finished");
return 3;
});
MessageBox.Show("asyncMethod finished");
}
asyncMethod().Wait(); // deadlock appears in Main Thread (UI)
MessageBox.Show("FakeMainThread finished");
in main button1_click() inside directli deadlock appears on UI. Thanks!
You don't experience a deadlock in the first code example because of the SynchronizationContext
. This context says what the await must do to restore your code. When you start a new task (Task.Run
), you will get the default context. In the button1_click
you get the context from the form.
The context for the form allows to execute only a single thread (the one used to paint/update your form).
When you call .Wait()
, then you keep that thread 'locked' with waiting until the task is done. That means that the thread is not released for another job and this is one of the reasons that the form goes into the 'not responding' state.
When you do an await [code]
, and that code is done, then it will ask the synchronization context to schedule the remainder of the code.
.Wait()
gets signaled that the task is done and continues.Avoiding this deadlock is the main reason that you most often get the suggestion to use ConfigureAwait(false)
, this tells the await
that it should substitute the current synchronization context with the default one, thus allowing you to use any free thread.
The only reason you don't want to use this (or say ConfigureAwait(true)
) is if you need to update something on your form (WPF or WinForms) or you need your http context (ASP.NET, not ASP.NET Core). This is because only a single thread has access to your form or http context. Here you don't get an exception on your MessageBox.Show
because this is 'outside' of the forms context and doesn't need this special thread/synchronization context.
See https://devblogs.microsoft.com/dotnet/configureawait-faq/ for more information.