简体   繁体   English

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

[英]Async-Await no deadlock where a deadlock is expected

It is known that synchronous waiting on an async method leads to deadlocks (see, for example Don't Block on Async Code ) 众所周知,异步方法的同步等待会导致死锁(例如,请参阅“ 不要阻止异步代码”

I have the following code in an event handler for a button-click in a Windows Forms application (ie the code is invoked with a UI SynchronizationContext installed). 我在事件处理程序中有以下代码,用于在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;

I fully expected the code to deadlock on clicking the button. 我完全希望代码在单击按钮时死锁。 However, what I actually see is synchronous waiting - the dialog becomes unresponsive for a while, and then accepts events as usual. 但是,我实际看到的是同步等待 - 对话框暂时没有响应,然后像往常一样接受事件。 I consistently see deadlocks when I try to synchronously wait on client async methods. 当我尝试同步等待客户端异步方法时,我一直看到死锁。 However, synchronously waiting on library async methods like SendAsync or ReadAsByteArrayAsync seems not to deadlock. 但是,同步等待SendAsyncReadAsByteArrayAsync异步方法似乎不会死锁。 Can someone explain this behaviour? 有人可以解释这种行为吗?

Don't implementations of async methods in .NET libraries use await statements internally, so that the continuations have to be marshalled back to the original SynchronizationContext? .NET库中的异步方法的实现是否在内部使用await语句,因此必须将continuation编组回原始的SynchronizationContext?

Note: If I define a client method, say 注意:如果我定义了一个客户端方法,请说

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();
}

and then say byte[] byteArray = wrapperMethod().Result; 然后说byte[] byteArray = wrapperMethod().Result; in the button click handler, I do obtain a deadlock. 在按钮单击处理程序中,我确实获得了死锁。

Don't implementations of async methods in .NET libraries use await statements internally? .NET库中的异步方法的实现是否在内部使用await语句?

Generally, no. 一般来说,没有。 I have yet to see a single implementation in the .NET framework that uses async-await internally. 我还没有在.NET框架中看到一个在内部使用async-await的实现。 It does use tasks and continuations but not the compiler magic the async and await keywords bring. 它确实使用任务和延续,但不使用asyncawait关键字带来的编译器魔法。

Using async-await is simple as the code looks synchronous but actually runs asynchronously. 使用async-await非常简单,因为代码看起来是同步的,但实际上是异步运行的。 But that simplicity has a very small price in performance. 但这种简单性在性能上的代价非常小

For most consumers this prices is worth paying, but the framework itself tries to be as performant as possible. 对于大多数消费者来说,这个价格是值得付出的,但框架本身也尽可能地高效。

However, synchronously waiting on library async methods like SendAsync or ReadAsByteArrayAsync seems not to deadlock. 但是,同步等待SendAsyncReadAsByteArrayAsync等库异步方法似乎不会死锁。

The deadlock is a result of the default behaviour of await. 死锁是await默认行为的结果。 When you await an uncompleted task the SynchronizationContext is captured and when it's completed the continuation is resumed on that SynchronizationContext (if it exists). 当您等待未完成的任务时,将捕获SynchronizationContext ,当它完成时,将在该SynchronizationContext上恢复继续(如果存在)。 When there's no async, await, captured SynchronizationContext , etc. this kind of deadlock can't happen. 当没有异步,等待,捕获的SynchronizationContext等时,这种死锁不会发生。

HttpClient.SendAsync specifically uses TaskCompletionSource to return a task without marking the method as async. HttpClient.SendAsync专门使用TaskCompletionSource返回任务,而不将方法标记为异步。 You can see that in the implementation on github here . 您可以在github上的实现中看到这一点

Most task-returning methods added to existing classes for async-await simply build a task using the already existing asynchronous API (ie BeginXXX / EndXXX ). 为async-await添加到现有类的大多数任务返回方法只是使用现有的异步API(即BeginXXX / EndXXX )构建任务。 For example this is TcpClient.ConnectAsync : 例如,这是TcpClient.ConnectAsync

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

When you do use async-await though you avoid the deadlock by using ConfigureAwait(false) when you don't need to capture the SynchronizationContext . 当您使用async-await虽然在不需要捕获SynchronizationContext时通过使用ConfigureAwait(false)来避免死锁。 It's recommended that libraries should alway use it unless the context is needed (eg a UI library). 除非需要上下文(例如UI库),否则建议库应该总是使用它。

You won't cause a deadlock by blocking on most out-of-the box Task -returning .NET calls because they wouldn't internally touch the SynchronizationContext that the Task was started on unless absolutely necessary (two reasons: performance and avoiding deadlocks). 你不会通过阻止大多数开箱即用的Task -returning .NET调用来导致死锁,因为除非绝对必要,否则它们不会在内部触及Task启动的SynchronizationContext (两个原因:性能和避免死锁) 。

What this means is that even if standard .NET calls did use async/await under the covers (i3arnon said they don't - I won't argue as I simply don't know), they would, beyond any doubt, use ConfigureAwait(false) unless capturing the context is definitely required. 这意味着即使标准的.NET调用确实使用了async/await (i3arnon说他们没有 - 我不会争辩,因为我根本就不知道),毫无疑问,他们会使用ConfigureAwait(false)除非捕获上下文是绝对必要的。

But that's the .NET framework. 但这就是.NET框架。 As for your own code, you will observe a deadlock if you call wrapperMethod().Wait() (or Result ) in your client (provided that you're running with a non-null SynchronizationContext.Current - if you're using Windows Forms, this will definitely be the case). 至于你自己的代码,如果你在客户端调用wrapperMethod().Wait() (或Result ),你会发现死锁(前提是你运行的是非空的SynchronizationContext.Current - 如果你使用的是Windows表格,这肯定是这样的)。 Why? 为什么? Because you're flaunting async/await best practices by not using ConfigureAwait(false) on your awaitables inside async methods that do not interact with the UI, causing the state machine to generate continuations that unnecessarily execute on the original SynchronizationContext . 因为你没有在不与UI交互的async方法中使用ConfigureAwait(false)来炫耀async/await最佳实践,导致状态机生成不必要地在原始SynchronizationContext上执行的延续。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM