I have configured the task
ConfigureAwait is for configuring awaits, not tasks. There's nothing that awaits the result of the ConfigureAwait, so that's an indication that it's being misused here.
I have configured the task not to continue on the UI thread after GetResult() returns.
Not really. As explained above, the ConfigureAwait has no effect here. More broadly, the code is (synchronously) blocking on the task, so the UI thread is blocked and then will resume executing after GetResult() returns.
This code will deadlock but I am not sure why.
Walk through it step by step. Read my blog post on async/await if you haven't already done so.
Note that this:
var result = obj.DoWorkAsync().ConfigureAwait(false).GetAwaiter().GetResult();
is pretty much the same as this:
var doWorkTask = obj.DoWorkAsync();
var result = doWorkTask.ConfigureAwait(false).GetAwaiter().GetResult();
- Asynchronous methods begin executing synchronously, just like any other method. In this case,
DoWorkAsync is called on the UI thread.
DoWorkAsync calls Task.Delay (also synchronously).
Task.Delay returns a task that is not complete; it will complete in 3 seconds. Still synchronous, and on the UI thread.
- The
await in DoWorkAsync checks to see if the task is complete. Since it is not complete, await captures the current context (the UI context), pauses the method, and returns an incomplete task.
- The calling code calls
ConfigureAwait(false) on the returned task. This essentially has no effect.
- The calling code calls
GetAwaiter().GetResult() on the (configured) returned task. This blocks the UI thread waiting on the task.
- 3 seconds later, the task returned by
Task.Delay completes, and the continuation of DoWorkAsync is scheduled to the context captured by its await - the UI context.
- Deadlock. The continuation is waiting for the UI thread to be free, and the UI thread is waiting for the task to complete.
In summary, a top-level ConfigureAwait(false) is insufficient. There are several approaches to sync-over-async code, with the ideal being "don't do it at all; use await instead". If you want to directly block, you need to apply ConfigureAwait(false) on every await on the method that is called (DoWorkAsync in this case), as well as the transitive closure of all methods called starting from there.
Clearly, this is a maintenance burden, and occasionally impossible (i.e., third-party libraries missing a ConfigureAwait(false)), and that's why I don't usually recommend this approach.