众所周知,异步方法的同步等待会导致死锁 (参见,例如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;
我完全希望代码在单击按钮时死锁。但是,我实际看到的是同步等待 - 对话框暂时没有响应,然后像往常一样接受事件。
当我尝试同步等待客户端异步方法时,我一直看到死锁。但是,同步等待库异步方法(如SendAsync
或ReadAsByteArrayAsync
)似乎不会死锁。有人可以解释这种行为吗?
.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;
,我确实获得了死锁。
答案 0 :(得分:4)
.NET库中的异步方法实现不是在内部使用await语句吗?
一般来说,没有。我还没有在.NET框架中看到一个在内部使用async-await的实现。它确实使用了任务和延续,但没有使用async
和await
关键字带来的编译魔术。
使用async-await非常简单,因为代码看起来是同步的,但实际上是异步运行的。但这种简单性在性能上具有非常小的价格。
对于大多数消费者而言,这个价格是值得付出的,但框架本身也尽可能地保持高效。
但是,同步等待
SendAsync
或ReadAsByteArrayAsync
之类的库异步方法似乎不会死锁。
死锁是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.Current
在async/await
内部的ConfigureAwait(false)
方法中不使用async
炫耀SynchronizationContext
最佳做法,而这些方法不会与UI进行交互,从而导致状态机器生成不必要地在原始flex-direction: row
上执行的延续。