在 Parallel.ForEach 循环中使数据库调用异步会提高性能吗?
Would making DB calls async inside a Parallel.ForEach loop improve performance?
使用 Parallel.ForEach 时,将任何数据库或 Api 调用转换为异步方法会提高性能吗?
一些背景知识,我目前有一个控制台应用程序,它按顺序循环访问一堆文件,并为每个文件调用一个 API 并进行一些数据库调用。主要逻辑如下所示:
foreach (file in files)
{
ReadTheFileAndComputeAFewThings(file);
CallAWebService(file);
MakeAFewDbCalls(file);
}
目前所有的数据库和网络服务调用都是同步的。
将循环更改为使用 Parallel.ForEach
使我的性能大幅提升,正如您所期望的那样。
我想知道我是否将 Parallel.ForEach
调用保留在那里,并在循环内将所有 Web 服务调用更改为异步(例如,HttpClient.SendAsync
)并将数据库调用更改为异步(使用 Dapper,db.ExecuteAsync()
) - 这会通过允许应用程序重用线程来提高应用程序的性能吗?还是它实际上什么都不做,因为 Parallel.ForEach
无论如何都会处理线程分配?
Parallel.ForEach 对任务而不是线程进行操作。这意味着它可以产生比线程池中的线程更多的任务。在这种情况下,使用异步方法可以通过使用更少的线程执行所有任务来优化性能。
https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel.foreach?view=netcore-3.1
The Parallel.ForEach method may use more tasks than threads over the lifetime of its execution, as existing tasks complete and are replaced by new tasks. This gives the underlying TaskScheduler object the chance to add, change, or remove threads that service the loop.
原创
foreach (file in files)
{
ReadTheFileAndComputeAFewThings(file);
CallAWebService(file);
MakeAFewDbCalls(file);
}
原始+异步(比上面更好,视情况而定!)
foreach (file in files)
{
await ReadTheFileAndComputeAFewThings(file);
await CallAWebService(file);
await MakeAFewDbCalls(file);
}
如果调用实际上没有实现 async ,这不会更好,然后会更糟。
另一种情况会更糟的是,如果异步性太短,它们就会超过 Task 的成本。
每个异步任务,创建一个托管线程,从系统反向 1mb,并添加线程同步时间。
尽管同步非常低,但如果这是在紧密循环中完成的,则会出现性能问题。
这里的关键是任务实际上必须是异步版本。
SaveChanges 与 SaveChangesAsync
读取与 ReadAsync
并行(比上面更好,视情况而定!)
Parallel.ForEach(files, item)
{
ReadTheFileAndComputeAFewThings(item);
CallAWebService(item);
MakeAFewDbCalls(item);
}
如果这一切可以同时发生,那就更好了。
也只有当你想分配多个线程、资源时,记住资源是有限的,你的机器只有那么多内核和内存,你会想要根据硬件负责的其他内容来管理它。
如果方法不是线程安全的,那就更好了。
并行 + 异步(比上面更好,视情况而定!)
Parallel.ForEach(files, item)
{
await ReadTheFileAndComputeAFewThings(item);
await CallAWebService(item);
await MakeAFewDbCalls(item);
}
仅供参考 - 上面的并行 + 异步示例实际上是不正确的!!!
由于 Parallel.ForEach 本身不是异步的,因此您需要研究如何构建 Parallel.ForEach
的异步版本
以上相同的评论在结合使用时也适用。
更新
根据评论,这在很大程度上取决于是否设置了 ConfigureAwait(),
但假设你还没有。此外,这不会按顺序执行,因此如果 CallAWebService 依赖于 ReadTheFileAndComputeAFewThings,那么事情可能会出错。
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());
}
或...
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());
两者之间的区别在于后者有更多的任务,你可能 运行 与后者有关上下文的问题... 又名枚举器将不再有正确的 "index" 文件,如果一个调用依赖于另一个调用首先完成。
惊人的link 解释异步...
https://docs.microsoft.com/en-us/archive/blogs/benwilli/tasks-are-still-not-threads-and-async-is-not-parallel
答案是 No. Asynchrony offers scalability, not performance. It allows to do the same job with less memory (each blocked thread = 浪费内存)。
It’s important to keep in mind, though, that asynchronicity is not a performance optimization for an individual operation. Taking a synchronous operation and making it asynchronous will invariably degrade the performance of that one operation, as it still needs to accomplish everything that the synchronous operation did, but now with additional constraints and considerations.
Parallel
class 用于 CPU 绑定作业。对于部分或完全 I/O-bound 的作业,最好使用异步 API,并且最好独立处理 CPU 绑定和 I/O-bound 部分,因为它们的最佳并发级别通常不同。 TPL Dataflow library. You can create a pipeline of Dataflow blocks linked to each other, and each block can be configured with a different MaxDegreeOfParallelism
是完成此类工作的近乎完美的工具。对于 CPU 绑定的部分,您受到运行应用程序的机器的 processors/cores 数量的限制。对于 I/O-bound 部分,您受到远程 Web 服务器、磁盘驱动器或数据库服务器功能的限制。
使用 Parallel.ForEach 时,将任何数据库或 Api 调用转换为异步方法会提高性能吗?
一些背景知识,我目前有一个控制台应用程序,它按顺序循环访问一堆文件,并为每个文件调用一个 API 并进行一些数据库调用。主要逻辑如下所示:
foreach (file in files)
{
ReadTheFileAndComputeAFewThings(file);
CallAWebService(file);
MakeAFewDbCalls(file);
}
目前所有的数据库和网络服务调用都是同步的。
将循环更改为使用 Parallel.ForEach
使我的性能大幅提升,正如您所期望的那样。
我想知道我是否将 Parallel.ForEach
调用保留在那里,并在循环内将所有 Web 服务调用更改为异步(例如,HttpClient.SendAsync
)并将数据库调用更改为异步(使用 Dapper,db.ExecuteAsync()
) - 这会通过允许应用程序重用线程来提高应用程序的性能吗?还是它实际上什么都不做,因为 Parallel.ForEach
无论如何都会处理线程分配?
Parallel.ForEach 对任务而不是线程进行操作。这意味着它可以产生比线程池中的线程更多的任务。在这种情况下,使用异步方法可以通过使用更少的线程执行所有任务来优化性能。
https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel.foreach?view=netcore-3.1
The Parallel.ForEach method may use more tasks than threads over the lifetime of its execution, as existing tasks complete and are replaced by new tasks. This gives the underlying TaskScheduler object the chance to add, change, or remove threads that service the loop.
原创
foreach (file in files)
{
ReadTheFileAndComputeAFewThings(file);
CallAWebService(file);
MakeAFewDbCalls(file);
}
原始+异步(比上面更好,视情况而定!)
foreach (file in files)
{
await ReadTheFileAndComputeAFewThings(file);
await CallAWebService(file);
await MakeAFewDbCalls(file);
}
如果调用实际上没有实现 async ,这不会更好,然后会更糟。 另一种情况会更糟的是,如果异步性太短,它们就会超过 Task 的成本。 每个异步任务,创建一个托管线程,从系统反向 1mb,并添加线程同步时间。 尽管同步非常低,但如果这是在紧密循环中完成的,则会出现性能问题。
这里的关键是任务实际上必须是异步版本。
SaveChanges 与 SaveChangesAsync
读取与 ReadAsync
并行(比上面更好,视情况而定!)
Parallel.ForEach(files, item)
{
ReadTheFileAndComputeAFewThings(item);
CallAWebService(item);
MakeAFewDbCalls(item);
}
如果这一切可以同时发生,那就更好了。 也只有当你想分配多个线程、资源时,记住资源是有限的,你的机器只有那么多内核和内存,你会想要根据硬件负责的其他内容来管理它。
如果方法不是线程安全的,那就更好了。
并行 + 异步(比上面更好,视情况而定!)
Parallel.ForEach(files, item)
{
await ReadTheFileAndComputeAFewThings(item);
await CallAWebService(item);
await MakeAFewDbCalls(item);
}
仅供参考 - 上面的并行 + 异步示例实际上是不正确的!!! 由于 Parallel.ForEach 本身不是异步的,因此您需要研究如何构建 Parallel.ForEach
的异步版本以上相同的评论在结合使用时也适用。
更新
根据评论,这在很大程度上取决于是否设置了 ConfigureAwait(), 但假设你还没有。此外,这不会按顺序执行,因此如果 CallAWebService 依赖于 ReadTheFileAndComputeAFewThings,那么事情可能会出错。
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());
}
或...
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());
两者之间的区别在于后者有更多的任务,你可能 运行 与后者有关上下文的问题... 又名枚举器将不再有正确的 "index" 文件,如果一个调用依赖于另一个调用首先完成。
惊人的link 解释异步... https://docs.microsoft.com/en-us/archive/blogs/benwilli/tasks-are-still-not-threads-and-async-is-not-parallel
答案是 No. Asynchrony offers scalability, not performance. It allows to do the same job with less memory (each blocked thread =
It’s important to keep in mind, though, that asynchronicity is not a performance optimization for an individual operation. Taking a synchronous operation and making it asynchronous will invariably degrade the performance of that one operation, as it still needs to accomplish everything that the synchronous operation did, but now with additional constraints and considerations.
Parallel
class 用于 CPU 绑定作业。对于部分或完全 I/O-bound 的作业,最好使用异步 API,并且最好独立处理 CPU 绑定和 I/O-bound 部分,因为它们的最佳并发级别通常不同。 TPL Dataflow library. You can create a pipeline of Dataflow blocks linked to each other, and each block can be configured with a different MaxDegreeOfParallelism
是完成此类工作的近乎完美的工具。对于 CPU 绑定的部分,您受到运行应用程序的机器的 processors/cores 数量的限制。对于 I/O-bound 部分,您受到远程 Web 服务器、磁盘驱动器或数据库服务器功能的限制。