我在.net核心应用程序中需要调用异步方法来处理大量对象。我们使用Parallel.ForEach做到了这一点,因此我们可以利用并行性更快地完成工作。
服务方法是async
方法,我们无法更改。我的问题是使用TPL并行时调用此方法的正确方法是什么。
这是一个简化的代码段(出于演示目的,我传递了迭代号而不是对象),以及我们的观察结果:
CallSendAsync方法在内部向API发出HTTP请求(使用HttpClient)。
private void ParallelFor()
{
Parallel.For(0, 100000, i =>
{
CallSendAsync(i).GetAwaiter().GetResult();
});
}
我上面这段代码的问题是,它使用的是GetAwaiter
,它使异步方法同步。但是,以上实现非常快。似乎也可以更有效地管理系统资源。
另一方面,我有这个:
private void ParallelForWithAsync()
{
Parallel.For(0, 100000, async i =>
{
await CallSendAsync(i);
});
}
此代码具有异步/等待功能。但是它变得非常慢,性能显着下降。它打开了大量的出站端口,最终HTTP请求错误。
第三,我也尝试过:
private void TaskWaitAll()
{
IEnumerable<int> arr = Enumerable.Range(1, 100000);
Task.WhenAll(
arr
.Select(CallSendAsync))
.GetAwaiter()
.GetResult();
}
其结果与第二个片段相似。
答案 0 :(得分:3)
我在.net核心应用程序中需要调用异步方法来处理大量对象。我们使用Parallel.ForEach做到了这一点,因此我们可以利用并行性更快地完成工作。
让我在那里停下来。您不会“使用并行”来“使事情变得更快”。并行是一种并发形式(一次执行一件事以上),并且是一种并发形式,它使用多个线程在多核计算机上更快地处理CPU绑定算法。但是,您的操作完全不受CPU的限制。它受I / O限制,这表明Parallel是用于此目的的错误技术。
如果要在基于I / O的处理中同时处理多个项目,则适当的解决方案是使用Task.WhenAll
。
无论速度如何变慢,性能都会大大降低。它打开了大量的出站端口,最终HTTP请求错误。
是的。如果您实际上同时发出十万个HTTP请求,这是可以预期的。请记住,少于IANA ephemeral ports少于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);
}