【问题标题】:Task.Run to increase parallelism of IO-bound operations?Task.Run 增加 IO 绑定操作的并行性?
【发布时间】:2018-07-03 17:19:19
【问题描述】:

我对@9​​87654321@ 以及我在互联网上读到的所有内容感到有些困惑。所以这是我的情况:我有一些处理传入套接字数据的函数:

public async Task Handle(Client client)
{
    while (true)
    {
        var data = await client.ReadAsync();
        await this.ProcessData(client, data);
    }
}

但这有一个缺点,我只能在处理完最后一个请求后才能读取下一个请求。所以这里有一个修改版:

public async Task Handle(Client client)
{
    while (true)
    {
        var data = await client.ReadAsync();
        Task.Run(async () => {
            await this.ProcessData(client, data);
        });            
    }
}

这是一个简化的版本。对于更高级的,我当然会限制并行请求的最大数量。

无论如何,这个ProcessData 主要是 IO 绑定的(对 dbs 进行一些调用,非常轻量级的处理并将数据发送回client)但我一直在阅读我应该将Task.Run 与 CPU 绑定函数一起使用。

对于我的情况,Task.Run 的正确用法是否正确?如果没有,还有什么替代方案?

【问题讨论】:

  • @Servy 如何在没有Task.Run 的情况下在后台运行异步函数?问题不在于线程。我看不出链接的帖子是如何相关的。如果您有答案,请随时发布。
  • @freakish 你不需要在后台线程中运行异步函数根本,因此你不需要使用Task.Run,因为它纯粹存在在后台线程中运行同步方法(您不需要这样做)。您的问题 is 是关于线程的,显然是关于并行性的。它是关于线程的,因为Task.Run 的存在是为了在另一个线程中运行一个方法,它是关于并行性,因为您希望并行运行三个操作。您可以查看我的其他答案,了解为什么您根本不需要线程来并行运行操作。
  • @Servy 不过,这确实没有回答评论。问题基本上是“我如何最好地让这个函数运行,以便可以使用下一个客户端(而不是等到这个客户端完成处理后再继续)?” OP 最好用“使用 Task.Run”回答。
  • @freakish 它不会(必然)在后台线程中运行,不。它是异步运行的(根据定义,这当然是正确的,因为它是异步的)。该方法在工作完成之前返回,并且该工作将在未来的某个时间完成而无需您的交互这意味着操作是异步的
  • @freakish 不同的异步操作可以通过多种方式实现它们的异步。他们可以使用线程,或者他们可能根本不需要任何线程来完成他们的工作,就像 IO 绑定工作的情况一样。

标签: c# async-await task-parallel-library


【解决方案1】:

从概念上讲,这是Task.Run 的一个很好的用法。这与 ASP.NET 分派请求的方式非常相似:(异步)读取请求,然后分派(同步或异步)工作到线程池。

在实践中,您需要确保正确处理ProcessData 的结果。特别是,您需要观察异常。就目前的代码而言,来自ProcessData 的任何异常都将被吞没,因为从Task.Run 返回的任务未被观察到。

IMO,处理每条消息错误的最简单方法是拥有自己的try/catch,例如:

public async Task Handle(Client client)
{
  while (true)
  {
    var data = await client.ReadAsync();
    Task.Run(async () => {
      try { await this.ProcessData(client, data); }
      catch (Exception ex) {
        // TODO: handle
      }
    });            
  }
}

// TODO: handle 是适合您的应用程序的错误处理代码。例如,您可能会在套接字上发送错误响应,或者只是记录并忽略。

【讨论】:

  • 我知道将 IO 操作封装在 Task.Run() 中是个坏主意。考虑到这一点,假设我们在 ASP.NET 应用程序中有多个 IO 操作(没有 async apis 的 DB 或 WCF 调用),并且使用 Task.WhenAll(task1_DB, task2_WCF, task3_DB...) 等待它们。请注意,所有任务都是 IO 绑定的。你推荐使用Task.Run()吗?如果有,怎么做?
  • @NrN:如果你能避免的话,就不会。最好的解决方案是使您的 DB 和 WCF 调用异步。 Task.Run 会为每个请求消耗额外的线程池线程,这会对您的可扩展性产生负面影响。
  • 感谢您的回复。如果 DB 或 WCF 等底层操作不是真正的 async,则 MVC 控制器不是 async 是否可以。我们可以让控制器动作方法保持同步吗?或者我们应该用Task.FromResult 包装结果以使操作方法async?另外,你有什么建议awaiting Task.FromResult
  • @NrN:如果你所有的底层操作都是同步的,那么你的 API 端点应该是同步的。
  • @StephenCleary,我对这个答案有点困惑。您说如果 IO 绑定调用是真正异步的,那么调用 Task.Run 将消耗额外的线程池线程。那么为什么在这种情况下使用 Task.Run 来包装异步 IO 操作是个好主意呢?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多