【问题标题】:Should I make a fast operation async if the method is already async如果方法已经是异步的,我应该使快速操作异步吗
【发布时间】:2016-06-30 08:42:15
【问题描述】:

我有这段代码(不重要的细节是它在 AWS 的 EC2 实例上运行,在 SQS 队列上处理消息)。

方法中的第一个语句通过 http 获取一些数据,第二个语句将状态保存到本地 dynamo 数据存储。

public bool HandleMessage(OrderAcceptedMessage message)
{
    var order = _orderHttpClient.GetById(message.OrderId);
    _localDynamoRepo.SaveAcceptedOrder(message, order);
    return true;
}

性能特点是http往返耗时100-200毫秒,dynamo写入耗时10毫秒左右。

这两个操作都有async 版本。我们可以这样写:

public async Task<bool> HandleMessage(OrderAcceptedMessage message)
{
    var order = await _orderHttpClient.GetByIdAsync(message.OrderId);
    await _localDynamoRepo.SaveAcceptedOrderAsync(message, order);
    return true;
}

因此指导是,由于第一个操作“可能需要超过 50 毫秒的时间来执行”,它应该使用 async 和 await。 (1)

但是第二个快速操作呢?这两个论点哪个是正确的:

不要让它异步:它不符合 50ms 标准,不值得开销。

不要让它异步:开销已经由之前的操作支付。已经有基于任务的异步发生了,值得使用。

1) http://blog.stephencleary.com/2013/04/ui-guidelines-for-async.html

【问题讨论】:

    标签: c# asynchronous async-await


    【解决方案1】:

    不重要的细节是它在 AWS 的 EC2 实例上运行,在 SQS 队列上处理消息

    实际上,我认为这是一个重要的细节。因为这不是一个 UI 应用程序;它是一个服务器应用程序。

    指导是因为第一个操作“可能需要超过 50 毫秒才能执行”

    本指南仅适用于 UI 应用程序。因此,50ms 准则在这里毫无意义。

    这两个论点哪个是正确的

    异步与速度无关。这是关于释放线程。 UI 应用程序的 50 毫秒指南就是关于释放 UI 线程。在服务器端,async 是关于释放线程池线程的。

    问题是您想要/需要多少可扩展性?如果您的后端是可扩展的,那么我通常推荐async,因为这样可以释放线程池线程。这使您的 Web 应用程序更具可扩展性,并且能够更快地对负载变化做出反应。但这只有在您的后端可以与您的网络应用程序一起扩展时才能为您带来好处。

    【讨论】:

    • 感谢您的回复。是的,这是一个服务器应用程序,在峰值时具有显着的负载和并发水平。 “释放线程池线程”是否有等效的 ms 指导数,还是“总是”?
    • @Anthony:如果您的应用程序负载很重并且您的后端可以扩展,我只会说“总是”。我不知道任何指标。在这种情况下,async 所做的只是让您最大限度地利用线程池。
    【解决方案2】:

    首先请注意,在 Web 应用程序中,异步的最大代价是降低了生产力。这就是我们正在权衡的好处。你需要考虑如果你让这个方法异步会有多少代码被感染。

    好处是在通话期间节省了一个线程。一个 200 毫秒的 HTTP 调用是异步的一个很好的例子(虽然不能肯定地说,因为它还取决于你执行调用的频率)。

    50ms 标准不是硬数字。事实上,该建议适用于实时 UI 应用。

    一个更有用的数字是latency times frequency。这告诉您长期平均消耗了多少线程。不频繁调用不需要优化。

    每秒 100 次 dynamo 调用在 10 毫秒内出现在一个线程阻塞处。这不重要。所以这可能不是异步的好候选。

    当然,如果您将第一个调用设为异步,您也可以将第二个调用设为异步,几乎不会增加生产力成本,因为一切都已被感染。

    您可以自己计算数字并据此决定。

    【讨论】:

      【解决方案3】:

      这可能会导致自以为是的讨论......但让我们尝试一下。 tl;dr:是的,保持异步。

      你在一个库中并且你不关心同步上下文,所以你不应该捕获它并将你的代码更改为:

      var order = await _orderHttpClient.GetByIdAsync(message.OrderId).ConfigureAwait(false);
      await _localDynamoRepo.SaveAcceptedOrderAsync(message, order).ConfigureAwait(false);
      

      此外:在第一次awaited 调用之后,您可能最终会进入线程池的一个线程。所以即使你使用非异步版本SaveAcceptedOrder() 它也不会阻塞。但是,这不是您应该依赖的并且您不一定知道异步方法的类型(CPU 限制或 IO 限制 =“async by design”)。如果它是 IO 绑定的,则无需在线程上运行。

      【讨论】:

      • 我不完全清楚ConfigureAwait 默认行为是什么——如果没有指定,设置是什么?
      • 默认为true。有关更多详细信息,请参见此处的“配置上下文”部分msdn.microsoft.com/en-us/magazine/jj991977.aspx
      【解决方案4】:

      如果您要进行 any 远程调用,请将其设为异步。是的,DynamoDB 调用速度很快(除非有一个低于标准的哈希键,或者单个表中有许多 GB 的数据),但您仍然通过 Internet 进行调用(即使您在 AWS EC2 等内部),因此您不应忽略任何 Eight Fallacies of Distributed Computing - 尤其是 1)网络可靠或 2)延迟为零。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-06-21
        • 2019-12-02
        • 2013-11-03
        • 2014-11-27
        • 2023-03-16
        相关资源
        最近更新 更多