为什么等待任务有时会阻塞?
Why do awaitable Tasks sometimes block?
我想我对 async/await 对的工作方式有一个普遍的误解。我正在使用 EasyNetQ 方法(C# 中 RabbitMQ 的接口),我正在尝试调用我创建的以下方法:
public Task<U> RequestDirectReply<T, U>(T request, int timeout) where T : class where U : class
{
using (var bus = RabbitHutch.CreateBus($"virtualHost=MyVirtualHost;timeout={timeout};host=MyHostName"))
{
return bus.RequestAsync<T, U>(request);
}
}
按照我的理解,我应该能够调用此方法,从 RequestAsync 获取任务,然后执行一系列操作,然后在完成这些操作后等待该任务。像这样:
Task<Reply> task = RequestDirectReply<Request, Reply>(msg, 10);
for (int i = 0; i < 1000000000; ++i)
{
// Hi, I'm counting to a billion
}
var reply = await task;
但是,程序会在超时持续时间内阻止对 RequestAsync 的调用,而不是等待。然后await立即抛出超时异常。
为了看看我是不是误会了,我也尝试了以下方法:
public async Task<U> RequestDirectReply<T, U>(T request, int timeout) where T : class where U : class
{
using (var bus = RabbitHutch.CreateBus($"virtualHost=MyVirtualHost;timeout={timeout};host=MyHostName"))
{
return await bus.RequestAsync<T, U>(request);
}
}
同样的事情。它阻塞在 RequestAsync 上。这与常规阻塞同步调用有何不同?
async
不保证代码实际上会 运行 异步或不会阻塞调用线程。理想情况下,它应该立即开始操作并 return 如您所料返回给调用者,但有时(即使在 .Net Framework 的现有方法中)步骤并不完全异步。
样本:
async Task<int> MyAsync()
{
Thread.Sleep(1000); // (1) sync wait on calling thread
await Task.Delay(1000); // (2) async wait off calling thread
Thread.Sleep(1000); // (3) sync wait likely on original thread
}
Sleep
总是阻塞调用线程。在 async
方法中第一次 await
调用后 return 编辑要等待的任务。这是您可能观察到的案例的演示。
- 异步等待,此时没有线程用于该方法。会根据 SynchronizationContext 返回一个线程。在大多数情况下 - 原始 UI/request 线程。
- Sleep 需要阻塞一些线程。根据是否将 SynchronizationContext 设置为 return 到原始线程,可能会在启动调用的线程上等待。
假设您按照应有的方式使用 async/await
模式,像这样的问题通常是您使用的第三方代码的错误。
您正在进行的 RequestAsync()
调用被委托给 Rpc.Request(...)
方法 code here,它在返回任务之前做了很多工作。我最好的猜测是它所做的一些涉及计时器的工作最终会阻塞,这意味着您对方法本身的调用也会阻塞。
我想我对 async/await 对的工作方式有一个普遍的误解。我正在使用 EasyNetQ 方法(C# 中 RabbitMQ 的接口),我正在尝试调用我创建的以下方法:
public Task<U> RequestDirectReply<T, U>(T request, int timeout) where T : class where U : class
{
using (var bus = RabbitHutch.CreateBus($"virtualHost=MyVirtualHost;timeout={timeout};host=MyHostName"))
{
return bus.RequestAsync<T, U>(request);
}
}
按照我的理解,我应该能够调用此方法,从 RequestAsync 获取任务,然后执行一系列操作,然后在完成这些操作后等待该任务。像这样:
Task<Reply> task = RequestDirectReply<Request, Reply>(msg, 10);
for (int i = 0; i < 1000000000; ++i)
{
// Hi, I'm counting to a billion
}
var reply = await task;
但是,程序会在超时持续时间内阻止对 RequestAsync 的调用,而不是等待。然后await立即抛出超时异常。
为了看看我是不是误会了,我也尝试了以下方法:
public async Task<U> RequestDirectReply<T, U>(T request, int timeout) where T : class where U : class
{
using (var bus = RabbitHutch.CreateBus($"virtualHost=MyVirtualHost;timeout={timeout};host=MyHostName"))
{
return await bus.RequestAsync<T, U>(request);
}
}
同样的事情。它阻塞在 RequestAsync 上。这与常规阻塞同步调用有何不同?
async
不保证代码实际上会 运行 异步或不会阻塞调用线程。理想情况下,它应该立即开始操作并 return 如您所料返回给调用者,但有时(即使在 .Net Framework 的现有方法中)步骤并不完全异步。
样本:
async Task<int> MyAsync()
{
Thread.Sleep(1000); // (1) sync wait on calling thread
await Task.Delay(1000); // (2) async wait off calling thread
Thread.Sleep(1000); // (3) sync wait likely on original thread
}
Sleep
总是阻塞调用线程。在async
方法中第一次await
调用后 return 编辑要等待的任务。这是您可能观察到的案例的演示。- 异步等待,此时没有线程用于该方法。会根据 SynchronizationContext 返回一个线程。在大多数情况下 - 原始 UI/request 线程。
- Sleep 需要阻塞一些线程。根据是否将 SynchronizationContext 设置为 return 到原始线程,可能会在启动调用的线程上等待。
假设您按照应有的方式使用 async/await
模式,像这样的问题通常是您使用的第三方代码的错误。
您正在进行的 RequestAsync()
调用被委托给 Rpc.Request(...)
方法 code here,它在返回任务之前做了很多工作。我最好的猜测是它所做的一些涉及计时器的工作最终会阻塞,这意味着您对方法本身的调用也会阻塞。