会使DB在Parallel.ForEach循环内调用异步来提高性能吗?

问题描述 投票:-1回答:3

使用Parallel.ForEach时,将任何DB或Api调用转换为异步方法会提高性能吗?

有点背景,我目前有一个控制台应用程序,该应用程序依次循环浏览一堆文件,并且每个文件都调用一个API并进行一些数据库调用。主要逻辑如下:

foreach (file in files)
{
    ReadTheFileAndComputeAFewThings(file);
    CallAWebService(file);
    MakeAFewDbCalls(file);
}

当前所有数据库和Web服务调用都是同步的。

正如您所期望的,更改循环以使用Parallel.ForEach使我获得了巨大的性能提升。

[我想知道是否将Parallel.ForEach调用保留在那里,并在循环内,将所有Web服务调用更改为异步(例如HttpClient.SendAsync),并将DB调用更改为异步(使用Dapper,db.ExecuteAsync()) -通过允许其重用线程来提高应用程序的性能吗?还是因为Parallel.ForEach仍然在照顾线程分配而实际上什么都不做?

c# parallel-processing task httpclient dapper
3个回答
2
投票

答案为No。异步提供可伸缩性,not performance。它允许用更少的内存来完成相同的工作(每个阻塞的线程=浪费的内存1 MB)。

但是请记住,异步并不是针对单个操作的性能优化。进行同步操作并使之异步将不可避免地降低该操作的性能,因为它仍需要完成同步操作所做的所有工作,但是现在需要附加的约束和注意事项。

Parallel类适用于CPU绑定的作业。对于部分或完全由I / O绑定的作业,最好使用异步API,并且理想情况下最好独立处理CPU绑定和I / O绑定的部分,因为它们的最佳并发级别通常是不同的。 TPL Dataflow库是用于这种工作的近乎完美的工具。您可以创建相互链接的数据流块的管道,并且每个块可以配置为不同的MaxDegreeOfParallelism。对于受CPU约束的部件,您受运行应用程序的计算机的处理器/内核数限制。对于受I / O约束的部件,您受到远程Web服务器,磁盘驱动器或数据库服务器的功能的限制。


1
投票

Parallel.ForEach操作任务,而不是线程。这意味着它可以产生比线程池中更多线程更多的任务。在这种情况下,使用异步方法可以通过使用更少的线程来完成所有任务来优化性能。

MaxDegreeOfParallelism

Parallel.ForEach方法在执行的整个生命周期内使用的任务可能比线程更多,因为现有任务已完成并被新任务替代。这使基础TaskScheduler对象有机会添加,更改或删除为循环服务的线程。


0
投票

原始

https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel.foreach?view=netcore-3.1

原始+异步(优于以上,取决于!)

foreach (file in files)
{
    ReadTheFileAndComputeAFewThings(file);
    CallAWebService(file);
    MakeAFewDbCalls(file);
}

如果调用实际上没有实现async,那就更好了,那就更糟了。另一种情况将变得更糟,即如果异步时间太短以至于超过了Task的成本。每个异步任务都会创建一个托管线程,该线程与系统反向1mb,并增加线程同步时间。如果同步在一个紧密的循环中进行,那么同步将非常低,它将看到性能问题。

关键是任务实际上必须是异步版本。

  • SaveChanges与SaveChangesAsync

  • Read vs ReadAsync


平行(优于以上,取决于!)

foreach (file in files)
{
   await ReadTheFileAndComputeAFewThings(file);
   await CallAWebService(file);
   await MakeAFewDbCalls(file);
}

如果这可以同时发生,那么更好。同样,仅当您要分配多个线程,资源,记住资源有限,计算机仅具有如此多的内核和内存时,才需要根据硬件负责的其他因素来管理它。

如果这些方法不是线程安全的,那就更好了。


并行+异步(优于以上,取决于!)

Parallel.ForEach(files, item) 
{
    ReadTheFileAndComputeAFewThings(item);
    CallAWebService(item);
    MakeAFewDbCalls(item);
}

FYI-上面的并行+异步示例实际上是不正确的!]由于Parallel.ForEach本身不是异步的,因此您将需要研究如何构建Parallel.ForEach的异步版本

此外,结合使用时,上述注释同样适用。

更新

基于注释,它主要取决于是否已设置ConfigureAwait(),但假设您还没有。同样,这不会按顺序执行,因此,如果CallAWebService依赖于ReadTheFileAndComputeAFewThings,那么事情可能会出错。

Parallel.ForEach(files, item) 
{
   await ReadTheFileAndComputeAFewThings(item);
   await CallAWebService(item);
   await MakeAFewDbCalls(item);
}

或...

foreach (file in files)
{
   List<Task> jobs = new List<Task>();
   jobs.Add(ReadTheFileAndComputeAFewThings(file))
   jobs.Add(CallAWebService(file))
   jobs.Add(MakeAFewDbCalls(file))
   Task.WhenAll(jobs.ToArray());
}

两者之间的差异是一个任务更多,而[[您可能会在后面的上下文中遇到问题 ..... aka枚举器将不再具有正确的“索引“提交,并且一个呼叫是否依赖于另一个呼叫首先完成。

解释异步的惊人链接... List<Task> jobs = new List<Task>(); foreach (file in files) { jobs .Add(ReadTheFileAndComputeAFewThings(file)) jobs .Add(CallAWebService(file)) jobs .Add(MakeAFewDbCalls(file)) } Task.WhenAll(jobs.ToArray());
© www.soinside.com 2019 - 2024. All rights reserved.