此 async/await 代码可能导致死锁?

Potential deadlock being caused by this async/await code?

我有以下 class:

public abstract class ServiceBusQueueService : IServiceBusQueueService
{
    private readonly string _sbConnect;

    protected ServiceBusQueueService(string sbConnect)
    {
        _sbConnect = sbConnect;
    }

    public async Task EnqueueMessage(IntegrationEvent message)
    {
        var topicClient = new TopicClient(_sbConnect, message.Topic, RetryPolicy.Default);
        await topicClient.SendAsync(message.ToServiceBusMessage());
    }
}

正在使用的是这样的:

public ulong CreateBooking()
{
     // Other code omitted for brevity 
     ulong bookingId = 12345; // Pretend this id is generated sequentially on each call

      _bookingServiceBusQueueService.EnqueueMessage(new BookingCreatedIntegrationEvent
      {
            BookingId = bookingId
      }).GetAwaiter().GetResult();

      return bookingId;
}

当从我的 CreateBooking 方法调用 EnqueueMessage 方法时,程序挂起并且在到达行 await topicClient.SendAsync(message.ToServiceBusMessage());

后不再继续运行

现在,当我如下更改对 EnqueueMessage 方法的调用时,代码可以正常工作,并且我的消息已成功推送到服务总线:

Task.Run(() => _bookingServiceBusQueueService.EnqueueMessage(new BookingCreatedIntegrationEvent
        {
            BookingId = bookingId
        })).Wait();

我对 async/await 的使用不是很熟悉,经过一些研究,这听起来像是造成了死锁,这是正确的吗?为什么将方法调用更改为 Task.Run(() => SomeAsyncMethod()).Wait(); 会导致它停止挂起并按预期工作?

您是对的,您看到的是异步死锁。最好的办法是始终等待任务返回函数,这样您的代码就是从上到下异步的。

您还可以通过在 EnqueueMessage.

中的 await 之后使用 .ConfigureAwait(false) 来避免此处的死锁

Task.Run 在这里修复它的原因是,它导致里面的委托 运行 没有当前的 SynchronizationContext,这就是 await 捕获的(当没有 .ConfigureAwait(false) 被使用)并在你阻塞结果时导致死锁。

假设我为您提供了以下工作流程:

  1. 写一张纸条,上面写着 "mow the lawn" 并把它放在冰箱上。
  2. 在笔记中提到的任务完成之前什么都不做。
  3. 做个三明治
  4. 完成冰箱上笔记上写的任务。

如果您遵循该工作流程,您将进入步骤 (2),然后永远什么都不做,因为您正在等待步骤 (1) 的任务完成,直到步骤 (4) 才开始).

您在软件中有效地编码了相同的工作流程,所以难怪您会永远等待。

那么为什么要加一个Run"fix"呢?这是另一个工作流程:

  1. 写一张纸条,上面写着 "mow the lawn",雇用一名工人,然后把纸条交给工人
  2. 在笔记中提到的任务完成之前绝对不要做任何事情
  3. 做个三明治

现在你不必永远等待。你等待工人修剪你的草坪,这只是 低效和浪费 。你可以在等待的时候做其他工作,比如做那个三明治。或者您可以自己修剪草坪,而不必承担雇用工人的费用。

这就是为什么您永远不会同步等待异步操作的原因。只有两种可能:如果以后在做异步操作,你就一直等下去,显然是坏掉了。如果不是,那你就是在浪费时间睡觉,而这显然是一种浪费。

按照设计用途使用异步:异步