Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

async/await deadlocking when using a SynchronizationContext

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 the SynchronizationContext.Current which gets the synchronization context for the current thread.
SynchronizationContext.Current is populated depending on the
environment you are in. The GetAwaiter 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 available SynchronizationContext.
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 because SynchronizationContext.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?

like image 391
nainaigu Avatar asked Dec 08 '15 08:12

nainaigu


People also ask

How can we avoid deadlock in async await?

You can use the following simple workaround in the scenario where you want to call an async method from a sync method: Before the call, clear the SynchronizationContext. Do the call, there will be no more deadlock here, wait for it to finish. Restore the SynchronizationContext.

Does async await run on separate thread?

An await expression in an async method doesn't block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method. The async and await keywords don't cause additional threads to be created.

What happens if we execute an asynchronous method but don't await it?

The call to the async method starts an asynchronous task. However, because no Await operator is applied, the program continues without waiting for the task to complete. In most cases, that behavior isn't expected.

Does await stop the main thread?

Because await is only valid inside async functions and modules, which themselves are asynchronous and return promises, the await expression never blocks the main thread and only defers execution of code that actually depends on the result, i.e. anything after the await expression.


1 Answers

The key point is that some SynchronizationContexts only allow a single thread to run code at the same time. One thread is calling Result or Wait. When the async methods wants to enter it can't.

Some SynchronizationContexts are mutli-threaded and the problem does not occur.

like image 131
usr Avatar answered Oct 22 '22 04:10

usr