3

众所周知,异步方法上的同步等待会导致死锁(例如,请参阅Don't Block on Async Code

我在 Windows 窗体应用程序中单击按钮的事件处理程序中有以下代码(即在SynchronizationContext安装 UI 的情况下调用代码)。

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, new Uri("http://www.google.com"));
Task<HttpResponseMessage> t = client.SendAsync(request);
t.Wait();
var response = t.Result;

我完全期望代码在单击按钮时死锁。但是,我实际看到的是同步等待——对话框有一段时间没有响应,然后像往常一样接受事件。当我尝试同步等待客户端异步方法时,我一直看到死锁。但是,同步等待异步方法,例如死锁SendAsyncReadAsByteArrayAsync似乎不会死锁。有人可以解释这种行为吗?

.NET 库中的异步方法的实现是否在内部使用 await 语句,因此必须将延续编组回原始 SynchronizationContext?

注意:如果我定义了一个客户端方法,比如说

public async Task<byte[]> wrapperMethod()
{
    var client = new HttpClient();
    var request = new HttpRequestMessage(HttpMethod.Get, new Uri("http://www.google.com"));
    var response = await client.SendAsync(request);
    return await response.Content.ReadAsByteArrayAsync();
}

然后byte[] byteArray = wrapperMethod().Result;在按钮单击处理程序中说,我确实遇到了死锁。

4

2 回答 2

4

.NET 库中异步方法的实现是否在内部使用 await 语句?

一般来说,没有。我还没有在 .NET 框架中看到一个在内部使用 async-await 的实现。它确实使用了任务和延续,但没有使用asyncandawait关键字带来的编译器魔法。

使用 async-await 很简单,因为代码看起来是同步的,但实际上是异步运行的。但是这种简单性在性能上的代价非常小。

对于大多数消费者来说,这个价格是值得付出的,但框架本身会尽可能地发挥性能。

但是,同步等待库异步方法,例如死锁SendAsyncReadAsByteArrayAsync似乎不会死锁。

死锁是 await 的默认行为的结果。当您等待未完成的任务SynchronizationContext时,它会被捕获,当它完成时,会继续继续SynchronizationContext(如果存在)。当没有异步、等待、捕获SynchronizationContext等时,这种死锁就不会发生。

HttpClient.SendAsync专门用于TaskCompletionSource返回任务而不将方法标记为异步。您可以在 github here上的实现中看到这一点。

大多数为 async-await 添加到现有类的任务返回方法只是使用现有的异步 API(即BeginXXX/ EndXXX)构建任务。例如这是TcpClient.ConnectAsync

public Task ConnectAsync(IPAddress address, int port)
{
    return Task.Factory.FromAsync(BeginConnect, EndConnect, address, port, null);
}

当您确实使用 async-await 时,虽然您在ConfigureAwait(false)不需要捕获SynchronizationContext. 建议库应该始终使用它,除非需要上下文(例如 UI 库)。

于 2015-12-26T10:44:58.403 回答
2

您不会通过阻止大多数开箱即用的返回 .NET 调用而导致死锁,因为除非绝对必要(两个原因:性能和避免死锁),否则Task它们不会在内部触及启动的SynchronizationContext那个。Task

这意味着即使标准 .NET 调用确实在幕后使用async/await(i3arnon 说它们没有——我不会争论,因为我根本不知道),毫无疑问,ConfigureAwait(false)除非捕获上下文,否则它们会使用绝对是必需的。

但这就是 .NET 框架。至于您自己的代码,如果您在客户端调用(or ),您观察到死锁(前提是您正在使用非 null 运行- 如果您使用的是 Windows 窗体,肯定会出现这种情况)。为什么?因为你没有在不与 UI 交互的方法中使用 awaitables 来炫耀最佳实践,导致状态机生成不必要地在原始.wrapperMethod().Wait()ResultSynchronizationContext.Currentasync/awaitConfigureAwait(false)asyncSynchronizationContext

于 2015-12-26T11:52:22.120 回答