Parallel.ForEach处理List<string>时返回重复或空数据的问题求解
问题根源
两个核心原因直接导致了你遇到的字符串错乱、集合内容异常问题:
- 共享
msg变量的跨线程读写冲突:你当前的msg是在Parallel.ForEach外部声明的公共变量,所有并行执行的任务都会同时对同一个msg做清空、赋值、拼接操作,不同线程的操作互相穿插覆盖,就会出现单条信息拼接多段其他任务的内容、重复内容等错乱情况。你提到q++逻辑正常,大概率是你已经对q的自增做了线程同步(比如用了Interlocked.Increment),但字符串操作没做同步才会出现差异。 response集合非线程安全:常规的List<T>等集合本身不支持多线程同时写入,多个任务同时调用Add方法时会出现内部索引计数错乱,导致元素重复添加、漏加、内容不一致。
修复方案
按照以下两步修改即可解决问题:
- 将
msg改为并行任务内部的局部变量,每个线程处理单独条目时只用自己的局部变量,完全避免跨线程共享冲突。 - 改用线程安全集合存储处理过程中的日志,并行执行完成后再统一整理到最终返回的
response中,避免多线程写入普通集合的异常。
修复后的参考代码如下:
// 引入线程安全集合存处理日志,无需额外加锁 ConcurrentBag<string> processLogs = new ConcurrentBag<string>(); int q = 0; var parallelResult= Parallel.ForEach(myList, new ParallelOptions { MaxDegreeOfParallelism = Convert.ToInt32(Math.Ceiling( (Environment.ProcessorCount * 0.75) * 3.0)) }, item => { // msg改为局部变量,每个线程独立使用,不会和其他线程冲突 string msg = item.fullName; var balance = users.balance(item.id); if (balance.amount > 0 && balance.isReady) { var sendResponse = users.send(item.id, balance.amount); tbeResponse data = JsonConvert.DeserializeObject<tbeResponse>(sendResponse); if (data.success) { _context.users.Add(item); msg += " added : " + sendResponse; // 保证q自增的线程安全,如果你之前已经处理过可以保留原有逻辑 Interlocked.Increment(ref q); } else { msg += "could not be added: " + sendResponse; } } else { msg += " balance=0"; } processLogs.Add(msg); }); if(parallelResult.IsCompleted) _context.SaveChanges(); // 并行处理完成后统一把日志写入response foreach(var log in processLogs) { response.Add(log); } response.Add($"{q} were added to the database. And need to be signed"); return Ok(response);
额外注意
如果你用的_context是EF的DbContext,需要注意DbContext本身也不支持多线程并行操作,当前并行调用Add的逻辑也有概率触发异常,如果后续出现数据存储相关问题,建议把Add操作移到并行执行完成后统一处理,或者对Add操作加锁。
内容的提问来源于stack exchange,提问作者Ariel
相关产品推荐
相关产品推荐

