为何自定义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>实现批次拆分逻辑。
解决方案
- 替换原有的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; } } }
- 修改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
相关产品推荐
相关产品推荐

