这可能已经在网络上的某个地方得到了回答,但我看了很多例子,但我仍然无法弄清楚。
我有一个基于同步的服务层边界(例如“ProcessOrder”方法)。在此方法中,它执行各种功能,其中一些具有异步重载,并且在包装 I/O 绑定函数时使用它们是有意义的。
但是,我的困惑有两个。据我读到的:
因此,使用如下示例代码(注意:_publisher 是具有异步发送方法的 Azure QueueClient 的包装器):
public void ProcessOrder()
{
// other stuff...
_publisher.PublishAsync(new ItemOrderedEvent()
{
MessageId = Guid.NewGuid(),
IsDurable = true,
}).Wait();
}
似乎不符合“一路异步”规则,并且由于此模式在高负载下线程池耗尽,因此网络上有关于死锁等的警告,但代码如下:
public async void ProcessOrder()
{
// other stuff...
await _publisher.PublishAsync(new ItemOrderedEvent()
{
MessageId = Guid.NewGuid(),
IsDurable = true,
});
}
打破了“异步标记方法不应返回 void”规则,并且不会处理异常,因为根据我的理解,没有任务上下文可以将它们从异步方法中冒泡回来。注意:这是一个服务边界 - 调用代码可能是 UI,响应 MSMQ 消息、Web 服务调用等,因此我不希望服务调用的“实现”泄漏到更高层。在当前情况下,IMO,这仅是同步调用是有意义的。
发布者方法定义为:
public async Task PublishAsync(IMessageBusMessage message)
{
// do azure stuff
var _queue = QueueClient.CreateFromConnectionString(connectionString, "ItemOrdered");
await _queue.SendAsync(message);
}
所以我的问题是......你到底如何在基于同步的架构中使用合法的 I/O 绑定异步方法?典型的答案似乎是“你不能”(正如这个从同步上下文调用异步方法所建议的那样),但是我一定错过了一些东西,因为想要卸载 I/O 绑定工作并让当前线程在不基于 UI 的上下文中执行其他一些工作。
注意:这是一个服务边界 - 调用代码可能是 UI,响应 MSMQ 消息、Web 服务调用等,所以我不希望服务调用的“实现”泄漏到更高层。在当前情况下,IMO,这仅是同步调用是有意义的。
事实上,你搞反了。 :)
考虑这个方法声明:
public void ProcessOrder();
这绝对是同步的。如果它执行任何异步操作,它将被阻塞,因此它会表现得像同步一样。可以将其解释为异步的唯一方法是,如果它采用某种回调或与事件或类似的东西配对,在这种情况下,它无疑是异步的。
现在考虑这个方法声明:
public Task ProcessOrderAsync();
这很可能是异步的。但如果它返回一个已经完成的任务,它可能是同步的。 Await 有一个“快速路径”快捷方式,它用来处理这种情况,并且实际上将其视为同步。缓存是实际使用的一个例子;缓存命中是同步的。 因此,如果您想抽象实现的异步性,那么您应该使用 asynchronous 方法声明。
注意:Task
返回方法是(可能)异步声明,而
async
是实现细节。
也就是说,有多种方法可以将异步代码“包装”到同步 API 中,但是每种方法都有缺点,并且没有一种方法适用于所有场景。最好让你的 API 说实话:如果任何实现可能是异步的,那么方法声明也应该是异步的。 想要卸载 I/O 密集型工作似乎是完全合理的 让当前线程在非 UI 的上下文中执行一些其他工作 基于。
如果这是一个非 UI 线程,并且您确实有一些其他工作要做,而
您的 IO 绑定操作正在挂起,那么没有什么可以阻止您在同步方法中执行此操作:
public void ProcessOrder()
{
// other stuff...
// initiate the IO-bound operation
var task = _publisher.PublishAsync(new ItemOrderedEvent() {});
{
MessageId = Guid.NewGuid(),
IsDurable = true,
}); // do not call .Wait() here
// do your work
for (var i = i; i < 100; i++)
DoWorkItem(i);
// work is done, wait for the result of the IO-bound operation
task.Wait();
}
如果您无法重构所有代码以使用“一路异步”理念(这可能很耗时,但几乎总是可能的),这可能是有意义的。
到底如何在基于同步的架构中使用合法的 I/O 绑定异步方法?
鱼与熊掌不可兼得。你要么采用异步,然后必须“一直异步”,要么采用同步,然后阻塞。你可以旋转另一个线程并在那里执行阻塞 IO,但这是不好的做法,但这会浪费一个线程来阻塞,这样你就可以同时做更多的工作。
public void ProcessOrder()
{
// other stuff...
Task.Run(async () => await _publisher.PublishAsync(new ItemOrderedEvent()
{
MessageId = Guid.NewGuid(),
IsDurable = true,
}).ContinueWith((Task task) => //do continuation if you need);
}
但是将如此松散的线松开是非常糟糕的做法。
如果可能的话,我要么一直异步,要么使用阻塞同步调用(像您在原始帖子中所做的那样调用 Result 或 Wait)
public async Task ProcessOrder()
{
// other stuff...
await _publisher.PublishAsync(new ItemOrderedEvent()
{
MessageId = Guid.NewGuid(),
IsDurable = true,
});
}