为什么 ConfigureAwait(false) 不起作用而 Task.Run() 起作用?

Why ConfigureAwait(false) does not work while Task.Run() works?

我正在使用 .ConfigureAwait(false) 调用 async 库方法。但是,我仍然陷入僵局。 (我在 ASP.NET 控制器 API 中使用它) 但是,如果我使用包装到 Task.Run() 中的相同方法,它工作正常。

我的理解是,如果库方法没有在内部使用 ConfigureAwait 那么添加 ConfigureAwait 不会解决问题,因为在库调用中它会导致死锁(我们阻止它通过使用 .Result)。但是,如果是这种情况,为什么它在 Task.Run() 中工作,因为它将无法在相同的 context/thread.

中继续

这个article讲一下。顺便说一句,我读了很多 Stephen Cleary 的文章。但是,为什么 Task.Run() 有效是个谜。

代码片段:

// This Create Method results in Deadlock
public async Task<string> Create(MyConfig config)
{
        Document doc = await Client.CreateDocumentAsync(CollectionUri, config).ConfigureAwait(false);
        return doc.Id;
}

// Uses Task.Run() which works properly, why??
public string Create(MyConfig config)
{
    Document doc = Task.Run(() => Client.CreateDocumentAsync(CollectionUri, config)).Result;
    return doc.Id;
}

[HttpPost]
public ActionResult CreateConfig(MyConfig config)
{
     string id = Create(config).Result;
     return Json(id);
}

在第一个示例中,Client.CreateDocumentAsync 的实现陷入死锁,因为它试图使用当前的 SynchronizationContext 执行延续。

使用 Task.Run 时,将在 ThreadPool 线程上调用委托,这意味着将没有当前 SynchronizationContext,因此将使用 ThreadPool 线程恢复所有延续。这意味着它不会死锁。

出于兴趣,为什么您的 CreateConfig 方法不是异步的?大多数最新版本的 MVC 和 WebAPI 都支持异步方法,摆脱 .Result 将是最好的解决方案。

我相信 Lukazoid 是正确的。换句话说...

// This Create Method results in Deadlock
public async Task<string> Create(MyConfig config)
{
  Document doc = await Client.CreateDocumentAsync(CollectionUri, config).ConfigureAwait(false);
  return doc.Id;
}

您不能只将 ConfigureAwait(false) 固定在一个级别并让它神奇地防止死锁。 ConfigureAwait(false) 只有在该方法及其调用的所有方法的传递闭包中被每个 await 使用时才能防止死锁。

换句话说,ConfigureAwait(false) 需要用于 Create 中的每个 await(确实如此),并且还需要用于每个 awaitCreateDocumentAsync 中(我们不知道),它还需要用于 CreateDocumentAsync 调用的每个方法中的每个 await,等等

这是死锁问题如此脆弱 "solution" 的原因之一。

只是一个观察:我也注意到这样做也会导致死锁

private string Create(Task<Document> task)
{
    var doc = Task.Run(() => task).Result;
    return doc.Id;
}

[HttpPost]
public ActionResult CreateConfig(MyConfig config)
{
     var task = Client.CreateDocumentAsync(CollectionUri, config);
     var id = Create(task).Result;
     return Json(id);
}

所以即使运行线程池上的东西也未必是最终的解决方案。似乎同样重要的考虑因素是 SynchonizationContext 在创建 异步方法的任务 时生效。