仍然对 C# 中与 GetAwaiter 和 GetResult 一起使用的 ConfigureAwait(false) 感到困惑。遇到死锁或方法不返回

Dav*_*ave 3 c# asynchronous async-await configureawait

我已经阅读了:http : //blog.stephencleary.com/2012/07/dont-block-on-async-code.html以及即使在 Asp.Net 流中使用 ConfigureAwait(false) 之后也接受的死锁答案,但我只是太密集了,看不到发生了什么。

我有代码:

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());                
        }
Run Code Online (Sandbox Code Playgroud)

如您所见,CloseLoadDoor 被声明为异步的。我认为(特别是从上面的第一篇文章)如果我使用 ConfigureAwait(false) 我可以调用异步方法而不会出现死锁。但这就是我似乎得到的。对“CloseLoadDoor().ConfigureAwait(false).GetAwaiter().GetResult() 的调用永远不会返回!

我正在使用 GetAwaiter.GetResult 因为 CancelCalibration 不是异步方法。这是一个通过 MVVM 模式定义的按钮处理程序:

public ICommand CancelCalibrationCommand
        => _cancelCalibrationCommand ?? (_cancelCalibrationCommand = new DelegateCommand(CancelCalibration));
Run Code Online (Sandbox Code Playgroud)

如果有人要告诉我我可以使 CancelCalibration 异步,请告诉我如何。我可以添加async到方法声明中吗?但是,我仍然想知道为什么这种ConfigureAwait.GetAwaiter.GetResult模式给我带来了麻烦。我的理解是,这GetAwaiter.GetResult是一种在无法更改签名时从同步方法调用异步方法的方法。

我猜我并没有真正摆脱使用原始上下文,但是我做错了什么以及修复它的模式是什么?谢谢,戴夫

Ste*_*ary 9

我认为(特别是从上面的第一篇文章)如果我使用 ConfigureAwait(false) 我可以调用异步方法而不会出现死锁。

那篇文章中有一个重要的说明:

使用 ConfigureAwait(false) 来避免死锁是一种危险的做法。对于阻塞代码调用的所有方法的传递闭包中的每个等待,您必须使用 ConfigureAwait(false),包括所有第三方和第二方代码。使用 ConfigureAwait(false) 来避免死锁充其量只是一种黑客攻击)。

因此,被ConfigureAwait(false)用于每一个 await在传递闭包?这意味着:

  • 是否CloseLoadDoor使用ConfigureAwait(false)每一个await?我们可以从发布的代码中看到它确实如此。
  • 是否_model.CloseLoadDoor使用ConfigureAwait(false)每一个await?我们看不到的。
  • 是否通过所谓的每一个方法_model.CloseLoadDoor的使用ConfigureAwait(false),每await?
  • 是否通过每一个通过调用方法叫每个方法_model.CloseLoadDoor使用ConfigureAwait(false)每一个await?
  • 等等。

这至少是一个严重的维护负担。我怀疑在调用堆栈的某个地方,缺少一个ConfigureAwait(false).

正如该笔记总结的那样:

正如这篇文章的标题所指出的,更好的解决方案是“不要阻塞异步代码”。

换句话说,那篇文章的重点是“不要阻塞异步代码”。这不是说“用这个巧妙的技巧阻止异步代码”。

如果您确实想要一个同时支持同步和异步调用者的 API,我建议在我关于 brownfield async 的文章中使用 bool 参数 hack 。


附带说明一下,在代码中CloseLoadDoor().ConfigureAwait(false).GetAwaiter().GetResult(),ConfigureAwait不做任何事情。它是“配置等待”,而不是“配置任务”。因为没有await,所以ConfigureAwait(false)没有效果。

  • @Dave:是的,在“await Task.Run(...);”上缺少“ConfigureAwait(false)”肯定会导致这里出现死锁。`.ConfigureAwait(false).GetAwaiter().GetResult()` 的问题在于 `.ConfigureAwait(false)` 在那里毫无意义;它与没有“ConfigureAwait(false)”的“.GetAwaiter().GetResult()”完全相同。异步同步没有“完美”的解决方案,但将其包装在“Task.Run”中并对其进行阻塞将适用于“大多数”代码。 (3认同)