Search code examples
c#asynchronousasync-awaitconfigureawait

Still confused on ConfigureAwait(false) used with GetAwaiter and GetResult in C#. Getting a deadlock or method not returning


I have read: http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html and the accepted answer at deadlock even after using ConfigureAwait(false) in Asp.Net flow but am just too dense to see what is going on.

I have code:

private void CancelCalibration()
{
    // ...
    TaskResult closeDoorResult =  CloseLoadDoor().ConfigureAwait(false).GetAwaiter().GetResult(); 
    CalibrationState = CalibrationState.Idle;

    return;
    // ...                   
}

private async Task<TaskResult> CloseLoadDoor()
{       
    TaskResult result = await _model.CloseLoadDoor().ConfigureAwait(false);           
    return result;
}
public async Task<TaskResult> CloseLoadDoor()
    {
        TaskResult result = new TaskResult()
        {
            Explanation = "",
            Success = true
        };
        await _robotController.CloseLoadDoors().ConfigureAwait(false);
        return result;
    }
    public async Task CloseLoadDoors()
    {                         
            await Task.Run(() => _robot.CloseLoadDoors());              
    }

     public void CloseLoadDoors()
    {
   // syncronous code from here down              
   _doorController.CloseLoadDoors(_operationsManager.GetLoadDoorCalibration());                
        }

As you can see, CloseLoadDoor is declared async. I thought (especially from the first article above) that if I use ConfigureAwait(false) I could call an async method without a deadlock. But that is what I appear to get. The call to "CloseLoadDoor().ConfigureAwait(false).GetAwaiter().GetResult() never returns!

I'm using the GetAwaiter.GetResult because CancelCalibration is NOT an async method. It's a button handler defined via an MVVM pattern:

public ICommand CancelCalibrationCommand
        => _cancelCalibrationCommand ?? (_cancelCalibrationCommand = new DelegateCommand(CancelCalibration));

If someone is going to tell me that I can make CancelCalibration async, please tell me how. Can I just add async to the method declaration? HOWEVER, I'd still like to know why the ConfigureAwait.GetAwaiter.GetResult pattern is giving me trouble. My understanding was that GetAwaiter.GetResult was a way to call async method from syncronous methods when changing the signature is not an option.

I'm guessing I'm not really freeing myself from using the original context, but what am I doing wrong and what is the pattern to fix it? Thanks, Dave


Solution

  • I thought (especially from the first article above) that if I use ConfigureAwait(false) I could call an async method without a deadlock.

    There's an important note in that article:

    Using ConfigureAwait(false) to avoid deadlocks is a dangerous practice. You would have to use ConfigureAwait(false) for every await in the transitive closure of all methods called by the blocking code, including all third- and second-party code. Using ConfigureAwait(false) to avoid deadlock is at best just a hack).

    So, is ConfigureAwait(false) used for every await in the transitive closure? This means:

    • Does CloseLoadDoor use ConfigureAwait(false) for every await? We can see from the code posted that it does.
    • Does _model.CloseLoadDoor use ConfigureAwait(false) for every await? That we cannot see.
    • Does every method called by _model.CloseLoadDoor use ConfigureAwait(false) for every await?
    • Does every method called by every method called by _model.CloseLoadDoor use ConfigureAwait(false) for every await?
    • etc.

    This is a severe maintenance burden at least. I suspect that somewhere down the call stack, there's a missing ConfigureAwait(false).

    As that note concludes:

    As the title of this post points out, the better solution is “Don’t block on async code”.

    In other words, the whole point of that article is "Don't Block on Async Code". It's not saying "Block on Async Code with This One Neat Trick".

    If you do want to have an API that supports both synchronous and asynchronous callers, I recommend using the bool argument hack in my article on brownfield async.


    On a side note, in the code CloseLoadDoor().ConfigureAwait(false).GetAwaiter().GetResult(), the ConfigureAwait doesn't do anything. It's "configure await", not "configure task". Since there's no await there, the ConfigureAwait(false) has no effect.