从同步服务层边界调用异步方法

时间:2014-04-15 09:56:59

标签: c# asynchronous async-await c#-5.0

这可能已在网上的某个地方得到解答,但我看了很多例子,但我仍然无法弄明白。

我有一个基于同步的服务层边界(例如“ProcessOrder”方法)。在这个方法中,它执行各种函数,其中一些函数具有异步重载,并且在它们包装I / O绑定函数时使用它们是有意义的。

然而,我的困惑是双重的。从我读过的内容:

  • 异步代码应该是“异步一直向下”
  • 异步标记的方法不应返回void - 这是事件处理程序的边缘情况和
    不处理冒泡异常

因此,使用下面的示例代码(NB:_publisher是具有异步发送方法的Azure QueueClient的包装器):

public void ProcessOrder()
{
    // other stuff...

    _publisher.PublishAsync(new ItemOrderedEvent()
    {
        MessageId = Guid.NewGuid(),
        IsDurable = true,
    }).Wait();
}

似乎不适合“异步一直向下”规则,并且由于高负载下关于此模式的线程池耗尽,Web上存在关于死锁等的警告,但代码如:

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绑定异步方法?典型的答案似乎是“你不能”(正如这个Calling async methods from a synchronous context所建议的那样)但是我必须遗漏一些东西,因为想要卸载I / O绑定工作并让当前线程做的似乎是完全合理的其他一些工作,在非基于UI的环境中。

3 个答案:

答案 0 :(得分:2)

  

注意:这是一个服务边界 - 调用代码可能是UI,以响应MSMQ消息,Web服务调用等。所以我不希望"实现"服务电话泄漏率更高。在当前的上下文中,IMO只对同步调用才有意义。

实际上,你已经倒退了。 :)

考虑这个方法声明:

public void ProcessOrder();

这绝对是同步的。如果它执行任何异步操作,它将被阻止,因此它会像它的同步一样。它可以被解释为异步的唯一方法是它是否需要某种回调或与事件或类似事件配对,在这种情况下它将明确地是异步的。

现在考虑这个方法声明:

public Task ProcessOrderAsync();

这很可能是异步的。但如果返回已完成的任务,可能可能是同步的。 Await有一个"fast path" shortcut用于处理这种情况,实际上将其视为同步。缓存是在实践中使用它的一个例子;缓存命中是同步的。

因此,如果您想抽象出实现的异步性,那么您应该使用异步方法声明。

注意:Task - 返回方法是(可能)异步声明,而async是实现细节。

那就是there are a variety of ways to "wrap" asynchronous code into a synchronous API,但是每一个都有缺点,并且没有一个在每个场景中都有效。保持API说实话是最好的:如果任何实现可能是异步的,那么方法声明应该是异步的。

答案 1 :(得分:0)

  

如何在基于同步的架构中使用合法的I / O绑定异步方法?

你也不能吃蛋糕。你要么是异步的,那么你必须"一直异步#34;或者你去同步然后阻止。

你可以,这是不好的做法,转动另一个线程并在那里做阻塞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);

}

但剥离这样的线程是非常糟糕的做法。

如果可能的话,我会一直异步,或者使用阻止同步调用(在原始帖子中调用结果或等待)

public async Task ProcessOrder()
{
  // other stuff...

 await _publisher.PublishAsync(new ItemOrderedEvent()
 {
    MessageId = Guid.NewGuid(),
    IsDurable = true,
 });
}  

答案 2 :(得分:0)

  

想要卸载I / O绑定工作似乎是完全合理的   让当前线程在非UI的上下文中执行其他一些工作   基于

如果这是一个非UI线程,并且你还有其他工作要做,而你的IO绑定操作正在等待,那么没有什么能阻止你在同步方法中这样做:< / p>

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(); 
}

如果您无法负担重新计算所有代码的使用费用,这可能是有意义的。#34; async all down down&#34;哲学(这可能很耗时,但几乎总是可行的。)