我正在创建一个 .net 核心应用程序,我需要在其中调用async
方法来处理大量对象。我们已经在Parallel.ForEach
内完成了此操作,因此我们可以利用并行性更快地完成作业。
服务方法是我们无法更改的async
方法。我的问题是在使用 TPL 并行时调用此方法的正确方法是什么。
这是一个简化的代码片段(我传递迭代#而不是用于演示目的的对象),以及我们的观察结果:
CallSendAsync
方法在内部向 API 发出 HTTP 请求(使用 HttpClient)。
private void ParallelFor()
{
Parallel.For(0, 100000, i =>
{
CallSendAsync(i).GetAwaiter().GetResult();
});
}
我对上述代码的问题是,它使用的是使async
方法同步的GetAwaiter
。但是,上述实现速度非常快。它似乎也更有效地管理系统资源。
另一方面,我有这个:
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 绑定的,这表明并行是用于此的错误技术。
如果要在基于 I/O 的处理时同时处理多个项目,则适当的解决方案是使用Task.WhenAll
。
但是,它变得非常慢,性能会显着下降。 它打开了大量的出站端口,最终HTTP请求出错。
是的。如果您实际上同时发出十万个HTTP请求,这是可以预期的。请记住,IANA 临时端口少于 16k 个。对于像这样的大量请求,您可能希望将其限制为更合理的数量 - 例如一次 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);
}