Task.Run 增加 IO 绑定操作的并行性?

Task.Run to increase parallelism of IO-bound operations?

我对 Task.Run 和我在互联网上读到的所有内容感到有点困惑。所以这是我的情况:我有一些处理传入套接字数据的函数:

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 的用法是否正确?如果不是,还有什么替代方案?

从概念上讲,这是 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 是适合您的应用程序的错误处理代码。例如,您可能会在套接字上发送错误响应,或者只是记录并忽略。