You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Partitioner的ForEachAsync抛出ArgumentOutOfRangeException异常排查

解决ForEachAsync偶发System.ArgumentOutOfRangeException(partitionCount参数)问题

刚碰到过类似的坑,结合你描述的场景,咱们一步步理清楚:

问题场景复盘

服务器上跑了20个多线程应用,只有2个在新增多线程逻辑后,调用自定义AsyncHelper.ForEachAsync时偶发System.ArgumentOutOfRangeException,错误直接指向partitionCount参数。一开始大家都怀疑是并行度过高导致的,但排查后发现空列表才是真正的诱因——其他应用的ForEachAsync调用没碰到空集合场景,所以没触发这个问题。

常见的AsyncHelper.ForEachAsync实现(参考你提供的代码逻辑)

通常这类自定义实现会是这样的:

public static async Task ForEachAsync<T>(this IEnumerable<T> source, int partitionCount, Func<T, Task> body)
{
    var partitions = Partitioner.Create(source).GetPartitions(partitionCount);
    using (partitions)
    {
        await Task.WhenAll(
            from partition in partitions
            select Task.Run(async () =>
            {
                using (partition)
                {
                    while (partition.MoveNext())
                    {
                        await body(partition.Current);
                    }
                }
            }));
    }
}

问题根源

当传入的source是空列表时,Partitioner.Create(source).GetPartitions(partitionCount)尝试创建指定数量的分区,但对于空集合,分区器内部逻辑无法生成有效分区,此时如果partitionCount大于0(你调用时肯定传的是正数),就会触发参数越界异常——这个场景不是每次都出现,可能和线程调度、分区器内部的偶发逻辑有关,所以才是"偶发"。

解决方案

给你两个靠谱的解决思路:

思路1:调用前提前拦截空集合

在调用ForEachAsync之前先判断集合是否为空,为空直接跳过,避免进入有问题的逻辑:

var targetDataList = GetYourDataList();
// 空集合直接返回,不执行异步遍历
if (!targetDataList.Any())
{
    return;
}
await AsyncHelper.ForEachAsync(targetDataList, 4, async item => 
{
    // 你的业务处理逻辑
});

思路2:修改AsyncHelper的实现,内部处理空集合边界

在自定义的ForEachAsync方法里增加空集合判断,同时也可以加固partitionCount的合法性校验:

public static async Task ForEachAsync<T>(this IEnumerable<T> source, int partitionCount, Func<T, Task> body)
{
    // 先做参数合法性校验
    if (source == null)
        throw new ArgumentNullException(nameof(source));
    if (partitionCount <= 0)
        throw new ArgumentOutOfRangeException(nameof(partitionCount), "并行度必须大于0");
    
    // 空集合直接返回,无需后续处理
    if (!source.Any())
    {
        return;
    }
    
    var partitions = Partitioner.Create(source).GetPartitions(partitionCount);
    using (partitions)
    {
        await Task.WhenAll(
            from partition in partitions
            select Task.Run(async () =>
            {
                using (partition)
                {
                    while (partition.MoveNext())
                    {
                        await body(partition.Current);
                    }
                }
            }));
    }
}

补充说明

为什么只有这两个应用出问题?因为其他应用的业务逻辑里,传给ForEachAsync的集合从来不会为空,而这两个应用新增多线程逻辑后,出现了处理空数据的场景,才触发了这个隐藏的边界问题。

内容的提问来源于stack exchange,提问作者Gun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:47:27