C# 为什么等待任务有时会阻塞?

C# 为什么等待任务有时会阻塞?,c#,async-await,easynetq,C#,Async Await,Easynetq,我想我对异步/等待对的工作方式有一个普遍的误解。我使用的是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=MyVirtualHos

我想我对异步/等待对的工作方式有一个普遍的误解。我使用的是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);
    }
}
public Task RequestDirectReply(T请求,int超时),其中T:class,其中U:class
{
使用(var bus=RabbitHutch.CreateBus($“virtualHost=MyVirtualHost;timeout={timeout};host=MyHostName”))
{
返回bus.RequestAsync(请求);
}
}
现在我理解了这一点,我应该能够调用这个方法,从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;
Task Task=RequestDirectReply(msg,10);
对于(int i=0;i<100000000;++i)
{
//嗨,我数到十亿了
}
var reply=等待任务;
但是,程序会在调用RequestAsync时阻塞超时时间,而不是在等待时阻塞。然后等待立即抛出超时异常

为了确定我是否有误解,我还尝试了以下方法:

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);
    }
}
public async Task RequestDirectReply(T request,int timeout)其中T:class其中U:class
{
使用(var bus=RabbitHutch.CreateBus($“virtualHost=MyVirtualHost;timeout={timeout};host=MyHostName”))
{
返回wait bus.RequestAsync(请求);
}
}

同样的事情。它在RequestAsync上阻塞。这与常规的阻塞同步调用有何不同?

async
不保证代码实际将异步运行或不会阻塞调用线程。理想情况下,它应该立即启动操作并按预期返回调用方,但有时(即使在现有的.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
}
async任务MyAsync()
{
Thread.Sleep(1000);//(1)调用线程时同步等待
等待任务。延迟(1000);/(2)异步等待调用线程
Thread.Sleep(1000);//(3)原始线程上可能存在同步等待
}
  • Sleep
    总是阻止调用线程。要等待的任务在第一个
    await
    调用
    async
    方法后返回。这是您可能观察到的案例的演示
  • 异步等待,此时没有用于该方法的线程。将返回到线程,具体取决于SynchronizationContext。在大多数情况下-原始UI/请求线程
  • 睡眠需要阻止一些线索。根据SynchronizationContext设置是否返回到原始线程,启动调用的线程上可能会发生等待
    假设您使用的是
    async/await
    模式,这样的问题通常是您使用的第三方代码的错误


    您正在进行的
    RequestAsync()
    调用被委托给
    Rpc.Request(…)
    方法,该方法在返回任务之前要做大量的工作。我最好的猜测是,它所做的一些涉及计时器的工作最终会阻塞,这意味着对方法本身的调用也会阻塞。

    这可能是
    RequestAsync
    中的一个问题。为了简单起见,让我们假设其中有一个Thread.Sleep(10000),然后它自己进行异步调用。在这种情况下,您的请求呼叫将被阻塞10秒。因此,你将不得不进一步投资。可能它正在尝试同步打开连接,但由于超时而失败。最后的代码示例更好。在第一个例子中,完全可以在完成之前进行处理。Alexei的回答是正确的。但是,当您在某些API中遇到这种情况时,您可以通过添加
    await Task.Yield()来强制API退出线程
    before
    return wait bus.RequestAsync
    。好的,在查看代码之后,这似乎就是问题所在。结果表明,RequestAsync会在超时期间阻塞。如果我将超时设置为60,它会阻塞一分钟。如果我把它设为1,它会阻塞一秒钟。这有点违背了它的异步性。我想我会用Rx扩展来表示我的超时时间或其他什么…。@C.Williamson:我建议在你的库的GitHub页面上也粘贴一个问题。作者可能没有意识到他们的异步代码是阻塞的。