An async/await example that causes a deadlock
Take a look at this example, Stephen has a clear answer for you:
So this is what happens, starting with the top-level method (
Button1_Click
for UI /MyController.Get
for ASP.NET):
The top-level method calls
GetJsonAsync
(within the UI/ASP.NET context).
GetJsonAsync
starts the REST request by callingHttpClient.GetStringAsync
(still within the context).
GetStringAsync
returns an uncompletedTask
, indicating the REST request is not complete.
GetJsonAsync
awaits theTask
returned byGetStringAsync
. The context is captured and will be used to continue running theGetJsonAsync
method later.GetJsonAsync
returns an uncompletedTask
, indicating that theGetJsonAsync
method is not complete.The top-level method synchronously blocks on the
Task
returned byGetJsonAsync
. This blocks the context thread.... Eventually, the REST request will complete. This completes the
Task
that was returned byGetStringAsync
.The continuation for
GetJsonAsync
is now ready to run, and it waits for the context to be available so it can execute in the context.Deadlock. The top-level method is blocking the context thread, waiting for
GetJsonAsync
to complete, andGetJsonAsync
is waiting for the context to be free so it can complete. For the UI example, the "context" is the UI context; for the ASP.NET example, the "context" is the ASP.NET request context. This type of deadlock can be caused for either "context".
Another link you should read: Await, and UI, and deadlocks! Oh my!
Why does this async/await code NOT cause a deadlock?
await
returns to the original synchronization context, whether that is the UI thread (in desktop UI applications) or the request context in ASP.NET (not core).
In a GUI application, you'd have a deadlock because the UI thread was locked by .Result
. await
would await forever for this call to finish.
Console applications and ASP.NET Core have no synchronization context, so calling .Result
won't cause a deadlock.
PS for VS 15.3:
Visual Studio 2017 15.3 Preview 2 (gasp) allows asynchronous main applications. With it, You can write :
public static Task Main()
{
var length = await GetPageLengthAsync("http://csharpindepth.com");
Console.WriteLine(length);
}
Why does async await pattern in this console app caused a deadlock?
You never start your task in DoNothing.
https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?view=netframework-4.8#remarks
Separating task creation and execution
The Task class also provides constructors that initialize the task but that do not schedule it for execution. For performance reasons, the Task.Run or TaskFactory.StartNew method is the preferred mechanism for creating and scheduling computational tasks, but for scenarios where creation and scheduling must be separated, you can use the constructors and then call the Task.Start method to schedule the task for execution at a later time.
async/await deadlock caused by Thread.Sleep?
I see a couple red flags:
First, change Thread.Sleep(1000)
to await Task.Delay(1000)
so you're not blocking the thread. That may be part of your problem, although I don't see quite how at the moment.
Second, your Main
method:
static void Main(string[] args)
{
Log();
Console.ReadKey();
}
There are two issues:
Log()
returns aTask
that you are not observing to see when it has completed. You are probably getting a compiler warning about that.- Your
Main
method isvoid
, which means it can't useawait
.
The reason these are a problem is that the await
keyword will return when it acts on an incomplete Task
(and the rest of the method is signed up as the "continuation" to happen when after the waiting is done). Usually it returns its own Task
object, but if it can't because the method is void
, it returns nothing. But it still returns.
Because you're not doing anything with the Task
returned from Log()
, your Main
method continues and you never know if it completes.
I see you are using Console.ReadKey()
to keep the program running, which is "ok". But you could just change your Main
method to async Task
and await Log()
:
static async Task Main(string[] args)
{
await Log();
}
That is assuming you are using C# 7.1+, since that's when you could start returning a Task
from Main()
.
Also, you are creating 100,000 threads but only allowing one to work at any one time. Why not just create a regular for
or foreach
loop and do them one at a time?
for (var i = 0; i < 100000; i++)
{
await logger.Log(i, new LogMessage() { Message = oneKBMessage, TimeStamp = DateTime.Now });
}
Then you don't even need the locks - at least for this specific use case. If there are other cases where you will call it from different threads, then keep the locks.
How can blocking async calls cause a deadlock if async calls aren't necessarily executed on a different thread?
What I'm asking is, where is GetJsonAsync executing when it's called from Button1_Click if this call doesn't create a new thread for it to execute on. Before the await within GetJsonAsync, isn't it executing the Console.WriteLine(...) within the UI context still?
I recommend reading my async
intro. In summary:
Every asynchronous method begins executing synchronously. This code:
public void Button1_Click(...)
{
var jsonTask = GetJsonAsync(...);
textBox1.Text = jsonTask.Result;
}
calls GetJsonAsync
on the UI thread, and it does begin executing on the UI thread. It executes Console.WriteLine
on the UI thread, new
s up a client on the UI thread, and even calls GetStringAsync
on the UI thread. It gets a task back from that method, and then await
s it (I'm ignoring the ConfigureAwait(true)
for simplicity).
The await
is the point at which things may become asynchronous. The task isn't complete (i.e., the client hasn't received the string yet), so GetJsonAsync
returns an incomplete task to its caller. Then Button1_Click
blocks the UI thread, waiting for that task to complete (by calling .Result
).
So, the state is currently GetJsonAsync
is no longer running on the UI thread. It is not actually "running" anywhere.
Later, when that string result arrives, the task that was returned from GetStringAsync
is completed, and GetJsonAsync
needs to resume executing. It's not already on the UI thread; it's not anywhere at the moment. Since the await
captured a UI context, it will attempt to resume on that context (on the UI thread).
Deadlock while using async/await
What's blocking is not the task itself, it's the call to Result
. A Task
represents an asynchronous operation, but calling its Result
property, or calling Wait()
will block the current thread until the method returns. And in a lot of cases, it will cause a deadlock because the task is not able to complete with it's calling thread blocked!
To prevent that, chain the tasks asynchronously, using async
and await
private async void button1_Click(object sender, EventArgs e)
{
var result = await HeavyWorkAsync(); // <=== await
richTextBox1.AppendText(result);
}
Also, Task.Delay(10).Wait();
completely defeats the prupose of using tasks in the first place: that will block the current thread. If that's really what you want to do (and it's pretty unlikely), call Thread.Sleep(10);
instead, it will make your intent much clearer, and you will have less hoops to jump through. Or better, use await Task.Delay(10);
in an async method.
About ConfigureAwait
What exactly does ConfigureAwait(false)
do?
It removes the obligation for the continuation of the task to run in the same context as the caller of the task. In most cases that means that the continuation is no longer guaranteed to run on the same context. So if I have a method thad does Foo()
, waits a little then Bar()
like this one:
async Task DoStufAsync()
{
Foo();
await Task.Delay(10);
Bar(); // run in the same context as Foo()
}
I'm guaranteed Bar will run in the same context. If I had ConfigureAwait(false)
, it's no longer the case
async Task DoStufAsync()
{
Foo();
await Task.Delay(10).ConfigureAwait(false);
Bar(); // can run on another thread as Foo()
}
When you're using ConfigureAwait(false)
, you tell your program you dont mind about the context. It can solve some deadlocking problems, but isn't usually the right solution. The right solution is most likely never to wait for tasks in a blocking way, and being asynchronous all the way.
Why this async method don't cause deadlock on a thread?
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.
- In case of the default context, any free thread is taken and your code will continue, the task is marked as done, and thus the 'loop' in the
.Wait()
gets signaled that the task is done and continues. - In case of the forms context, there is only a single thread, thus the completion of this task is scheduled to run after the current code is done. This is the reason of the deadlock.
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.
Related Topics
Calling a Function from a String in C#
C# Json.Net Convention That Follows Ruby Property Naming Conventions
Send Http Post Request in .Net
Why Saving Changes to a Database Fails
Async/Await - When to Return a Task VS Void
Why Is Floating Point Arithmetic in C# Imprecise
Evaluating String "3*(4+2)" Yield Int 18
Multiple Levels in MVC Custom Routing
Best Way to Implement Keyboard Shortcuts in a Windows Forms Application
Difference Between the | and || or Operators
Parse Email Content from Quoted Reply
What Does the [Flags] Enum Attribute Mean in C#
C# Difference Between == and Equals()
When Do You Use the "This" Keyword