此 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)
被使用)并在你阻塞结果时导致死锁。
假设我为您提供了以下工作流程:
- 写一张纸条,上面写着 "mow the lawn" 并把它放在冰箱上。
- 在笔记中提到的任务完成之前什么都不做。
- 做个三明治
- 完成冰箱上笔记上写的任务。
如果您遵循该工作流程,您将进入步骤 (2),然后永远什么都不做,因为您正在等待步骤 (1) 的任务完成,直到步骤 (4) 才开始).
您在软件中有效地编码了相同的工作流程,所以难怪您会永远等待。
那么为什么要加一个Run
"fix"呢?这是另一个工作流程:
- 写一张纸条,上面写着 "mow the lawn",雇用一名工人,然后把纸条交给工人
- 在笔记中提到的任务完成之前绝对不要做任何事情
- 做个三明治
现在你不必永远等待。你等待工人修剪你的草坪,这只是 低效和浪费 。你可以在等待的时候做其他工作,比如做那个三明治。或者您可以自己修剪草坪,而不必承担雇用工人的费用。
这就是为什么您永远不会同步等待异步操作的原因。只有两种可能:如果以后你在做异步操作,你就一直等下去,显然是坏掉了。如果不是,那你就是在浪费时间睡觉,而这显然是一种浪费。
按照设计用途使用异步:异步。
我有以下 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
.
.ConfigureAwait(false)
来避免此处的死锁
Task.Run
在这里修复它的原因是,它导致里面的委托 运行 没有当前的 SynchronizationContext,这就是 await 捕获的(当没有 .ConfigureAwait(false)
被使用)并在你阻塞结果时导致死锁。
假设我为您提供了以下工作流程:
- 写一张纸条,上面写着 "mow the lawn" 并把它放在冰箱上。
- 在笔记中提到的任务完成之前什么都不做。
- 做个三明治
- 完成冰箱上笔记上写的任务。
如果您遵循该工作流程,您将进入步骤 (2),然后永远什么都不做,因为您正在等待步骤 (1) 的任务完成,直到步骤 (4) 才开始).
您在软件中有效地编码了相同的工作流程,所以难怪您会永远等待。
那么为什么要加一个Run
"fix"呢?这是另一个工作流程:
- 写一张纸条,上面写着 "mow the lawn",雇用一名工人,然后把纸条交给工人
- 在笔记中提到的任务完成之前绝对不要做任何事情
- 做个三明治
现在你不必永远等待。你等待工人修剪你的草坪,这只是 低效和浪费 。你可以在等待的时候做其他工作,比如做那个三明治。或者您可以自己修剪草坪,而不必承担雇用工人的费用。
这就是为什么您永远不会同步等待异步操作的原因。只有两种可能:如果以后你在做异步操作,你就一直等下去,显然是坏掉了。如果不是,那你就是在浪费时间睡觉,而这显然是一种浪费。
按照设计用途使用异步:异步。