异步 - 等待没有死锁的死锁

时间:2015-12-26 10:39:12

标签: c# asynchronous async-await deadlock synchronizationcontext

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

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

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语句,因此必须将continuation编组回原始的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;,我确实获得了死锁。

2 个答案:

答案 0 :(得分:4)

  

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

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

使用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库),否则建议库应该总是使用它。

答案 1 :(得分:2)

你不会通过阻止大多数开箱即用Task来导致死锁 - 返回.NET调用,因为他们不会在内部触摸{ {1}}除非绝对必要,否则SynchronizationContext已启动(两个原因:性能和避免死锁)。

这意味着,即使标准的.NET调用 使用Task已经掩盖了(i3arnon说他们不会 - 我不会因为我而争辩他们无疑会使用async/await,除非明确需要捕捉上下文。

但那是.NET框架。对于您自己的代码,如果您在客户端中调用ConfigureAwait(false)(或wrapperMethod().Wait()), 会发现死锁(假设您使用非null Result - 如果你正在使用Windows Forms,肯定会是这种情况)。为什么?因为您使用SynchronizationContext.Currentasync/await内部的ConfigureAwait(false)方法中不使用async炫耀SynchronizationContext最佳做法,而这些方法不会与UI进行交互,从而导致状态机器生成不必要地在原始flex-direction: row上执行的延续。