According to this link:
When you are awaiting on a method with await keyword, compiler generates bunch of code in behalf of you. One of the purposes of this action is to handle synchronization with the UI thread. The key
component of this feature is theSynchronizationContext.Current
which gets the synchronization context for the current thread.
SynchronizationContext.Current
is populated depending on the
environment you are in. TheGetAwaiter
method of Task looks up for
SynchronizationContext.Current
. If current synchronization context is not null, the continuation that gets passed to that awaiter will get posted back to that synchronization context.When consuming a method, which uses the new asynchronous language features, in a blocking fashion, you will end up with a deadlock if
you have an availableSynchronizationContext
. When you are consuming such methods in a blocking fashion (waiting on the Task with Wait method or taking the result directly from the Result property of the Task), you will block the main thread at the same time. When eventually the Task completes inside that method in the threadpool, it is going to invoke the continuation to post back to the main thread becauseSynchronizationContext.Current
is available and captured. But there is a problem here: the UI thread is blocked and you have a deadlock!
public class HomeController : Controller
{
public ViewResult CarsSync()
{
SampleAPIClient client = new SampleAPIClient();
var cars = client.GetCarsInAWrongWayAsync().Result;
return View("Index", model: cars);
}
}
public class SampleAPIClient
{
private const string ApiUri = "http://localhost:17257/api/cars";
public async Task<IEnumerable<Car>> GetCarsInAWrongWayAsync()
{
using (var client = new HttpClient())
{
var response = await client.GetAsync(ApiUri);
// Not the best way to handle it but will do the work for demo purposes
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsAsync<IEnumerable<Car>>();
}
}
}
I have trouble understanding the bolded part of the statement above, but when I test the code above, it deadlocks as expected. But I still can't understand why the UI thread is blocked?
In this case, what is the available SynchronizationContext
? Is it the UI thread?
The key point is that some
SynchronizationContext
s only allow a single thread to run code at the same time. One thread is callingResult
orWait
. When the async methods wants to enter it can't.Some
SynchronizationContext
s are mutli-threaded and the problem does not occur.I explain this in full in my own blog post, but to reiterate here...
await
by default will capture a current "context" and resume itsasync
method on that context. This context isSynchronizationContext.Current
unless it isnull
, in which case it isTaskScheduler.Current
.Deadlocks can occur when you have a one-thread-at-a-time
SynchronizationContext
and you block on a task representing asynchronous code (e.g., usingTask.Wait
orTask<T>.Result
). Note that it is the blocking that causes the deadlock, not just theSynchronizationContext
; the appropriate resolution (almost always) is to make the calling code asynchronous (e.g., replaceTask.Wait
/Task<T>.Result
withawait
). This is especially true on ASP.NET.Your example is running on ASP.NET; there is no UI thread.
The current
SynchronizationContext
should be an instance ofAspNetSynchronizationContext
, a context that represents an ASP.NET request. This context only allows one thread in at a time.So, walking through your example:
When a request comes in for this action,
CarsSync
will start executing within that request context. It proceeds to this line:which is essentially the same as this:
So, it proceeds to call into
GetCarsInAWrongWayAsync
, which runs until it hits its firstawait
(theGetAsync
call). At this point,GetCarsInAWrongWayAsync
captures its current context (the ASP.NET request context) and returns an incompleteTask<IEnumerable<Car>>
. When theGetAsync
download finishes,GetCarsInAWrongWayAsync
will resume executing on that ASP.NET request context and (eventually) complete the task it already returned.However, as soon as
GetCarsInAWrongWayAsync
returns the incomplete task,CarsSync
blocks the current thread, waiting for that task to complete. Note that the current thread is in that ASP.NET request context, soCarsSync
will preventGetCarsInAWrongWayAsync
from ever resuming execution, causing the deadlock.As a final note,
GetCarsInAWrongWayAsync
is an OK method. It would be better if it usedConfigureAwait(false)
, but it's not actually wrong.CarsSync
is the method causing the deadlock; it's call toTask<T>.Result
is wrong. The appropriate fix is to changeCarsSync
: