I recommend reading Stephen Cleary's blog post on this topic: Don't Block on Async Code which explains how the deadlock happen and how to avoid it.
Pay attention to the difference of "deadlock" and "blocked UI". Deadlock happens when two threads are waiting for each other, Blocked UI happens when UI thread is busy/blocked and cannot process UI messages. Here using ConfigureAwait(false) doesn't prevent "blocked UI", but prevents "deadlock".
When you write an async library methods which runs a task, to prevent a possible deadlock it's recommended to run your task by ConfigureAwait(false). It's to prevent deadlock even in case the users of your library get the result of your async method by calling Result or Wait.
ConfigureAwait(false) basically tells: not to come back to the original context after this line and continue execution on thread pool thread.
To understand it better, look at this example:
// UI code
1: private void button1_Click(object sender, EventArgs e)
2: {
3: var result = GetDataAsync().Result;
4: MessageBox.Show(result);
5: }
// A library code
6: public async Task<string> GetDataAsync()
7: {
8: await Task.Delay(1000);
9: return "Data";
10: }
Considering the following facts:
In Windows Forms, all UI code executes in a single thread; the UI thread.
Result method of the Task blocks the calling thread until the result of the task is ready.
This is what happens here:
- At line 3,
GetDataAsync will be called and executed in UI thread up to line 8.
- At line 8 a task will be created and will run on thread pool thread and await is telling after running that task, the execution should continue in the previously captured context (Which is UI thread context).
- An uncompleted task returns to the original caller (at line 3) which will be completed in future (after completing task of like 8 and then running line 9).
- Then the
Result method at line 3 will be executed, which blocks the task until it gets completed and final result of GetDataAsync is ready.
- When the awaited task of line 8 completes, the scheduler tries to run line 9 in the UI thread, but the UI thread is blocked! So line 9 cannot be executed and
GetDataAsync cannot be completed.
A deadlock happens: UI thread is waiting for GetDataAsync to complete, and GetDataAsync is waiting for main thread to be free to execute rest of the code.
To avoid the deadlock, instead of getting the result of async method by Wait or Result, you should use await. But as I mentioned earlier, in above scenario, if you as a library developer run your task (here Task.Delay) by ConfigureAwait(false) it prevents deadlock, because it tells not to come back to the original context after this line and continue execution on thread pool thread. So at line 8, it tells to continue in thread pool thread, so while the UI thread is blocked, but line 9 executes and returns data to UI thread and unblock it.
// UI code
1: private void button1_Click(object sender, EventArgs e)
2: {
3: var result = GetDataAsync().Result;
4: MessageBox.Show(result);
5: }
// A library code
6: public async Task<string> GetDataAsync()
7: {
8: await Task.Delay(1000).ConfigureAwait(false);
9: return "Data";
10: }
But again, keep in mind, as the UI developer, you should always use await for awaiting the Task and you should not use Result or Wait.