Following innocuous looking async / await code will cause deadlock.
private void button1_Click(object sender, EventArgs e) { Task<string> task1 = LongRunningProcess(); textBox1.Text = task1.Result; } public async Task<string> LongRunningProcess() { string txt = await Task.Run(() => { System.Threading.Thread.Sleep(5000); return "results"; } ); return txt; }
To understand why, lets go into what await does in the above case.
1 | Execution starts when the button is clicked and button1_Click event is fired on the UI thread. |
2 | The method, named LongRunningProcess, is invoked. |
3 | The lamda expression passed to Task.Run() executes in a separate thread (lets call it thread_B) |
4 | Now await keyword is encountered, rather than completing the rest of the method, control returns back to button1_Click event to continue execution after the call to long running method. |
5 | Calling task1.Result makes the current thread wait on thread_B to complete and provide the results. So UI thread is now waiting for thread_B to complete. |
6 | As thread_B completes the task, it has to run the remaining part of long running method. Run time ensures that this code executes on the right context. That is, if the initial part of long running method was executed on the UI thread, then remaining part will also be executed in the same thread context. |
7 | Therefore, thread_B now attempts to run the remaining part of long running method on UI thread, while UI thread is waiting for thread_B to finish. |
8 | As UI thread and thread_B are waiting for each other, this creates the deadlock. |
How to avoid deadlock?
In this case, deadlock can be avoided by anyone of the following ways:
1- Use await keyword while calling long running method. This makes the button click event asynchronous also.
private async void button1_Click(object sender, EventArgs e) { textBox1.Text = await LongRunningProcess(); }
2- Call ConfigureAwait(false), this will inform the run time that the remaining part of long running method doesn’t need to execute on the UI thread, it can continue running on the thread pool.
public async Task<string> LongRunningProcess() { string txt = await Task.Run(() => { System.Threading.Thread.Sleep(5000); return "results"; } ).ConfigureAwait(false); //avoids the deadlock return txt; }
Why thread contexts are synchronized?
Consider the UI Controls (Windows Forms or WPF), they are not thread safe. Therefore, any update to the UI controls must be done only from the UI thread. To take care of this, any remaining code after await keyword in an asynchronous method will also execute on the thread context which initiated the method call.
Similarly, in an ASP.Net application, running the code on same thread context is important because the Culture, Principal and other information of the request are stored in the thread.
How thread contexts are synchronized?
To manage all this tricky context synchronization, we have SynchronizationContext class in .Net. There are framework specific implementations (derived classes) for Windows Forms, WPF/Silverlight and ASP.Net which handle the SynchronizationContext in their own ways for the framework to function properly.
The Windows Forms implementation uses Control.Invoke method to accomplish this (more details here). For ASP.NET, execution takes place on a different thread but the context is captured and passed on to the new thread.
There is an excellent article on MSDN regarding the SynchronizationContext which I recommend for details.