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

为何自定义Take扩展始终取前3项,无法迭代到下一批数据?

问题描述

我正在做一个POC(概念验证),需要将字符串List拆分为批次并异步处理每一批。但运行程序时,始终只能获取前3项(符合设定的批大小)。我自己写了Take扩展方法,并尝试使用async/await模式,请求帮助实现迭代到下一批数据。


原代码

Program.cs

public class Program
{
    public static async Task Main(string[] args)
    {
        var obj = new Class1();
        List<string> fruits = new()
            {
                "1",
                "2",
                "3",
                "4",
                "5",
                "6",
                "7",
                "8",
                "9",
                "10"
            };
        
        await Class1.Start(fruits);
        Console.ReadLine();
    }
}

public class Class1
{
    private const int batchSize = 3;
    public static async Task Start(List<string> fruits)
    {
        if (fruits == null)
            return;

        var e = fruits.GetEnumerator();
        while (true)
        {    
            var batch = e.Take(3); // 始终只取前3项,无法迭代到下一批
            if (batch.Count == 0)
            {
                break;
            }
            await StartProcessing(batch);
        }
    }

    public static async Task StartProcessing(List<string> batch)
    {
        await Parallel.ForEachAsync(batch, async (item, CancellationToken) =>
        {
            var list = new List<string>();
            await Task.Delay(1000);
            Console.WriteLine($"Fruit Name: {item}");
            list.Add(item);
        });
    }
}

Extension.cs

public static class Extensions
{
    public static List<T> Take<T>(this IEnumerator<T> e, int num)
    {
        List<T> list = new List<T>(num);
        int taken = 0;
        while (taken < num && e.MoveNext())
        {
            list.Add(e.Current);
            taken++;
        }

        return list;
    }
}

问题原因

你的代码中,List<T>.GetEnumerator()返回的是值类型枚举器(List<T>.Enumerator),当你把它作为IEnumerator<T>接口参数传递给Take扩展方法时,会触发装箱操作。值类型枚举器的状态无法在多次调用中可靠保留,导致每次调用Take都从列表开头重新读取数据。

手动操作枚举器本身就容易出错,更简单可靠的方案是直接针对IEnumerable<T>实现批次拆分逻辑。


解决方案

  1. 替换原有的Take扩展方法,实现一个通用的Batch批次拆分扩展:
public static class Extensions
{
    public static IEnumerable<List<T>> Batch<T>(this IEnumerable<T> source, int batchSize)
    {
        if (source == null) throw new ArgumentNullException(nameof(source));
        if (batchSize <= 0) throw new ArgumentOutOfRangeException(nameof(batchSize));

        List<T> currentBatch = new List<T>(batchSize);
        foreach (var item in source)
        {
            currentBatch.Add(item);
            if (currentBatch.Count == batchSize)
            {
                yield return currentBatch;
                currentBatch = new List<T>(batchSize);
            }
        }

        // 返回最后一批不足batchSize的数据
        if (currentBatch.Count > 0)
        {
            yield return currentBatch;
        }
    }
}
  1. 修改Class1的Start方法,使用新的Batch扩展迭代批次:
public class Class1
{
    private const int batchSize = 3;
    public static async Task Start(List<string> fruits)
    {
        if (fruits == null)
            return;

        // 遍历拆分后的每一批数据
        foreach (var batch in fruits.Batch(batchSize))
        {
            await StartProcessing(batch);
        }
    }

    public static async Task StartProcessing(List<string> batch)
    {
        await Parallel.ForEachAsync(batch, async (item, cancellationToken) =>
        {
            await Task.Delay(1000, cancellationToken);
            Console.WriteLine($"Fruit Name: {item}");
            // 原代码中的list未实际使用,可删除
        });
    }
}

额外优化说明

  • 移除了StartProcessing方法中无意义的List<string> list变量
  • 在Parallel.ForEachAsync中传入cancellationToken,增强任务的可取消性

修改后程序会依次处理所有批次:1-3、4-6、7-9、10,完全符合需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 09:57:21