C#使用Task.WhenAll并发AddRange操作List报数组长度不足异常
问题背景
我在代码中使用Task.WhenAll发起多个携带不同参数的异步请求,按药房ID筛选对应患者ID,拉取不同药房(pharmacies)的患者(patients)数据,计划将所有返回结果汇总到同一个列表后,再映射为其他业务模型。
接口请求默认开启分页,页大小通常设置为50。代码大部分场景运行正常,但偶发抛出如下异常:Destination array was not long enough. Check the destination index, length, and the array's lower bounds. (Parameter 'destinationArray')
约束要求:必须保留多请求异步并发执行的能力,不能改为串行调用。
原实现代码
var patients = new List<PatientDTO>(); await Task.WhenAll( pharmacyIds.Select(async pharmacyId => patients.AddRange(await _patientService.GetPatientsByClientIds(pharmacyId, recipes .Where(x => x.PharmacyId == pharmacyId) .Select(x => x.PatientId)))));
异常堆栈
at System.Array.Copy(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length, Boolean reliable) at System.Collections.Generic.List`1.Insert(Int32 index, T item) at System.Collections.Generic.List`1.InsertRange(Int32 index, IEnumerable`1 collection) at Coach.Domain.Queries.GetRecipeLogsQueryHandler.<>c__DisplayClass4_0.<<Handle>b__14>d.MoveNext()
问题根因
List<T>不是线程安全的集合,原生不支持多线程/多任务并发写入。
原代码中多个异步任务并发执行,请求完成后会同时调用共享patients列表的AddRange方法,存在明确的竞态条件:
- 多个任务同时判定列表剩余容量充足,同时向同一内存位置写入数据
- 某个任务触发列表扩容、替换内部存储数组时,其他任务仍向旧数组执行元素拷贝
- 多个任务同时修改列表内部的写入位置索引,导致索引计算偏差
你看到的数组长度不足错误就是这类竞态的典型表现。由于网络请求返回时间存在随机波动,多数场景下任务不会刚好同时执行写入操作,因此异常表现为偶发,和分页大小配置没有直接关联。
修复方案
不需要加锁,也不需要退化为串行调用,最优实现是避免并发任务直接写入共享集合:让每个异步任务仅返回自身的查询结果,等所有任务全部执行完成后,再在单一线程上下文中统一汇总所有结果,既完全保留并发能力,又没有锁开销:
// 并发发起所有查询任务,每个任务独立返回自己的结果集,不操作共享变量 var queryTasks = pharmacyIds.Select(async pharmacyId => await _patientService.GetPatientsByClientIds(pharmacyId, recipes .Where(x => x.PharmacyId == pharmacyId) .Select(x => x.PatientId)) ).ToArray(); // 等待所有查询请求完成 var queryResults = await Task.WhenAll(queryTasks); // 单线程汇总所有结果,不存在并发写入问题 var patients = queryResults.SelectMany(result => result).ToList();
该方案的优势:
- 所有查询请求完全并发执行,和原逻辑的并发性能完全一致,无额外耗时
- 从设计上消除了多任务共享写入的场景,彻底解决线程安全问题,不需要引入锁、线程安全集合等额外复杂度
- 代码逻辑清晰,后续维护成本低
如果一定要在任务执行过程中实时写入共享集合,也可以将List<PatientDTO>替换为线程安全的ConcurrentBag<PatientDTO>再执行写入,但性能和可读性均不如上述方案。
内容的提问来源于stack exchange,提问作者sina fayazi
相关产品推荐
相关产品推荐

