我有以下课程:
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;
}
从我的EnqueueMessage
方法中调用CreateBooking
方法时,程序挂起,并且在碰到行await topicClient.SendAsync(message.ToServiceBusMessage());
之后不再前进
现在,当我按以下方式更改对EnqueueMessage
方法的调用时,代码可以正常工作并且我的消息成功发送到服务总线:
Task.Run(() => _bookingServiceBusQueueService.EnqueueMessage(new BookingCreatedIntegrationEvent
{
BookingId = bookingId
})).Wait();
我对使用async / await不太熟悉,经过一番研究,听起来好像是造成了僵局,这是正确的吗?为什么将方法调用更改为Task.Run(() => SomeAsyncMethod()).Wait();
会导致它停止挂起并按预期工作?
答案 0 :(得分:10)
假设我给您提供了以下工作流程:
如果遵循该工作流程,则将转到步骤(2),然后永远不做任何事情,因为您正在等待步骤(1)的任务完成,该任务直到步骤(4)才开始。
您正在用软件有效地编码相同的工作流程,因此也就永远等待着也就不足为奇了。
那为什么要添加一个Run
“修复”呢?这是另一个工作流程:
现在您不会永远等待。您需要等待工人修剪草坪,这只是效率低下和浪费。您可以在等待时做其他工作,例如做三明治。或者,您可以自己修剪草坪,而不必承担雇用工人的费用。
这就是为什么您永远不会同步等待异步操作的原因。仅有两种可能性:如果您在将来进行异步操作,则您将永远等待,这显然被打破了。如果不是,那么您在进行工作时会浪费时间,这显然是浪费的。
按照设计的异步方式使用:异步。
答案 1 :(得分:1)
您是正确的,这是您看到的异步死锁。最好的做法是始终等待Task返回函数,以使您的代码从上到下都是异步的。
您也可以在.ConfigureAwait(false)
内部等待之后使用EnqueueMessage
来避免死锁。
Task.Run
在此进行修复的原因是,它导致内部的委托在没有当前SynchronizationContext的情况下运行,这就是等待状态捕获的(当不使用.ConfigureAwait(false)
时)并导致阻塞结果时陷入僵局。