c# - 在 Parallel.Foreach 中调用异步方法的最佳实践/正确方法是什么?
问题描述
我正在创建一个 .net 核心应用程序,我需要在其中调用一个async
方法来处理大量对象。我们已经在 a 内完成了这项工作,Parallel.ForEach
因此我们可以利用并行性更快地完成工作。
服务方法是async
我们无法改变的方法。我的问题是使用 TPL Parallel 时调用此方法的正确方法是什么。
这是一个简化的代码片段(我传递迭代 # 而不是用于演示目的的对象),以及我们的观察结果:
该CallSendAsync
方法在内部向 API 发出 HTTP 请求(使用 HttpClient)。
private void ParallelFor()
{
Parallel.For(0, 100000, i =>
{
CallSendAsync(i).GetAwaiter().GetResult();
});
}
我对上述代码的问题是,它使用了GetAwaiter
使async
方法同步的方法。然而,上面的实现非常快。它似乎也更有效地管理系统资源。
另一方面,我有这个:
private void ParallelForWithAsync()
{
Parallel.For(0, 100000, async i =>
{
await CallSendAsync(i);
});
}
此代码有一个async/await
. 但是它变得非常慢,性能显着下降。它打开了大量的出站端口,最终 HTTP 请求出错。
第三,我也试过这个:
private void TaskWaitAll()
{
IEnumerable<int> arr = Enumerable.Range(1, 100000);
Task.WhenAll(arr.Select(CallSendAsync)).GetAwaiter().GetResult();
}
这也有与第二个片段相似的结果。
解决方案
我需要调用异步方法来处理大量对象的 .net 核心应用程序。我们使用 Parallel.ForEach 完成了这项工作,因此我们可以利用并行性更快地完成工作。
让我在那儿阻止你。您不会“使用并行”来“使事情变得更快”。并行是并发的一种形式(一次做不止一件事),它是一种使用多个线程在多核机器上更快地处理 CPU 绑定算法的并发形式。但是,您的操作根本不受 CPU 限制;它是 I/O-bound,这表明 Parallel 是错误的技术。
如果您希望在基于 I/O 的处理过程中同时处理多个项目,则适当的解决方案是使用Task.WhenAll
.
但是它变得非常慢,性能显着下降。它打开了大量的出站端口,最终 HTTP 请求出错。
是的。如果您实际上同时发出十万个 HTTP 请求,这是可以预料的。请记住,少于 16k IANA 临时端口。对于这样的大量请求,您可能希望将其限制在一个更合理的数量 - 例如,一次 20 个。Parallel.For
将根据 CPU 使用率、线程池中的线程数等正确划分同步工作负载。要限制异步工作,您可以使用SemaphoreSlim
:
private async Task TaskWaitAll()
{
var mutex = new SemaphoreSlim(20);
IEnumerable<int> arr = Enumerable.Range(1, 100000);
var tasks = arr.Select(async i =>
{
await mutex.WaitAsync();
try { await CallSendAsync(i); }
finally { mutex.Release(); }
}).ToList();
await Task.WhenAll(tasks);
}
推荐阅读
- python - 大熊猫列读加列
- reactjs - React / Redux:如何更新网格的一个单元格而不重新渲染所有其他单元格
- java - 我需要了解样本中的 nEpoch 是什么
- airflow - 重新处理 Airflow 的历史数据
- c# - 如何访问 Blazor Web Assembly(客户端)中的服务或从实用程序类处理依赖注入?
- python - MemoryError((462855, 4998), dtype('uint8'))
- python - 读取带有 IP 地址列表的文本文件并建立 curl 连接
- sql-server - SSRS 2017 多选参数不适用于某些用户
- node.js - 将express与ejs一起使用时如何从磁盘或内存中加载资源
- javascript - 点击后如何动态更改css?然后再次单击返回默认值?