【问题标题】:Parllel Foreach vs Async Foreach for DB calls用于数据库调用的并行 Foreach 与异步 Foreach
【发布时间】:2016-01-02 05:56:52
【问题描述】:

我有一个场景,我必须为列表中的每个项目调用相同的数据库存储过程。我不想使用 foreach,因为它会降低性能,哪个是并行 foeach 或 async/await foreach 的最佳选择?

下面是示例代码

public Task<List<object>> CallMethod()
{
    foreach(var obj in input)
    {
        List.Add(await Task.Run(() =>CallDatabase(obj)));
    }
   return List;
}

public CallDatabase(object)
{
    //Code to call DB 
}

从 DB 接收到的所有对象都是独立的。

经过一些研究,我计划使用异步调用,这会提高性能吗?

【问题讨论】:

  • async 将如何提供帮助 - 在您继续之前,您不需要等待结果返回吗?
  • 不,我不必等待结果。一旦我从数据库中获取记录,我将构建对象并添加到列表中。客户端将收到从 DB 生成的对象列表。所有对象都是独立的。

标签: c# asynchronous foreach


【解决方案1】:

我已经实施了一个解决方案;不确定这个异步是否会提高性能,我对异步很陌生,所以不太清楚。

 public static async Task<List<Response>> Execute(Request objInput)
 {
       List<Response> objList = new List<Response>();
       foreach (object obj in objInput.objs)
        {
            objList.Add(await Task.Run(() =>GetDataFromDB(obj)));
        }
  return objList;
 }


  private static object GetDataFromDB(object obj)
  {
       //Call DB and build the object
  }

如果这不是实现异步的正确方法,请提供其他想法。

【讨论】:

  • 我认为您希望您的GetDataFromDB 也成为async,然后它应该调用async 与数据库通信的方法。那么您的Execute 方法中就不需要Task.Run
  • 我在某处读到 await (Task.Run) 会调用异步。对吗?
  • async 的想法是某事正在执行 IO,您希望在该 IO 发生时释放线程。当您使用await Task.Run 时,您只是将“等待”从一个线程推到另一个线程。
  • 如果您不介意,您可以发布一个如何做到这一点的示例吗?这件事很令人困惑。
【解决方案2】:

这主要是对D Stanley's answer 的评论——切换到并行/异步代码不太可能提高性能。

如果您主要关心的是响应性/可扩展性 - 异步会更好,因为通常数据库访问是 IO 绑定操作。它还允许在顺序处理和并行处理之间进行选择(即,如果您的 DB 层由于某种原因不支持同一连接上的并发请求)。此外,使用async,如果您使用默认同步上下文,则更容易获得正确的同步以更新 UI/请求。

顺序:它将与非异步解决方案一样长,但线程可以同时执行其他活动(对于 WinForms/WPF 等 UI 应用程序)或处理请求 (ASP.Net)。

async public Task<ResultType> CallMethodAsync()
{
    foreach(var obj in input)
    {
        var singleResult = await CallDatabaseAsync(obj);
        // combine results if needed
    }
    // return combined results    
}

并行:将同时运行所有请求,可能比顺序解决方案更快。

async public Task<ResultType> CallMethodAsync()
{
    List<Task<SingleResultType>> tasks = new List<Task<SingleResultType>>();
    foreach(var obj in input)
    {
        tasks.Add(await CallDatabaseAsync(obj));
    }
    await Task.WhenAll(tasks);

    foreach(SingleResultType result in tasks.Select(t=>t.Result))
    {
        // combine results if needed
    }
    // return combined results    
}

请注意,async 通常要求您的所有代码都是异步的 - 因此,如果您将一小段代码转换为并行运行,Parallel.ForEach 可能是更简单的解决方案,因为它不涉及处理 await vs Task.Wait - Deadlock?

【讨论】:

  • 感谢您的回答,我正计划实施这样的事情。我认为性能可能会有所提高,因为从数据库返回的所有对象都是独立的..
  • When.All 和 Task.Run 有什么区别??
【解决方案3】:

我不确定这是否会有所不同。我假设您仍然需要等待加载所有结果,在这种情况下async 没有帮助,您的瓶颈很可能是网络 I/O 和服务器处理而不是本地 CPU,所以并行性也无济于事。

也就是说,如果您不需要查询结果并且不在乎是否有错误,那么async 可能会在“即发即弃”的情况下有所帮助。

您最大的收获可能是尝试在一个查询中获得多个结果,而不是触发一堆单独的查询。

【讨论】:

  • 你不是在这里对架构做假设吗?数据库服务器可能是功能强大的机器,或者是集群的一部分,查询不会相互锁定,在这种情况下,并行运行查询可能会提高性能。
  • 最初的计划是在一次数据库调用中获取所有内容,但考虑到查询的复杂性,这是不可行的,
  • D Stanley, WhenAll 让你 await 我的帖子中显示了许多操作,但如果数据库访问已经最大限度地使用任何资源(网络、数据库),确实不太可能加快速度服务器,...)。
  • @Ananke 是的,我假设 I/O 比本地 CPU 更具瓶颈,我认为这是合理的,即使在集群环境中也是如此。并行仅通过利用多个本地内核来帮助 CPU 密集型操作。
【解决方案4】:

绝对是Async,因为Parallel.ForEach 用于计算密集型操作。它分布在可用的核心资源上并相应地编排它们。相反,异步仅用于此类操作:向服务发出请求,然后在之前请求的资源可用时继续并接收通知。

【讨论】:

  • 只是出于好奇,如果我在我的场景中使用并行 foreach 会发生什么......它会阻止下一个调用数据库的项目,直到第一个项目完成?
  • 不,它会阻塞单线程,等待你的数据库调用完成。所以你的程序会顺利运行,但是使用线程来做这些事情是浪费资源。
  • Parallel.ForEach 不适合 IO 绑定操作,因为每个线程都会阻塞,等待数据。如果异步操作可用于调用您的数据库,那么最好使用这些操作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-27
  • 1970-01-01
  • 2018-09-06
  • 2013-02-14
  • 1970-01-01
相关资源
最近更新 更多