C#中Parallel.For循环未执行完成代码提前运行引发空对象错误
Parallel.For 空对象错误与元素丢失问题解决方案
核心问题原因
首先明确:Parallel.For 本身是同步阻塞方法,会等待所有迭代执行完成后才会继续执行后续代码,你遇到的提前执行、空值、元素丢失问题本质是多线程环境下的线程安全违规,具体有两个核心错误点:
- 非线程安全的
Dictionary被多线程并发读写:普通Dictionary<TKey, TValue>不支持并发写入,多线程同时调用ContainsKey、Add时会触发竞态条件,轻则元素丢失、出现空值,重则直接抛出异常。 - 跨线程共享变量无同步:你在循环外层声明的
deck变量,在并行迭代中被多个线程同时修改赋值,会导致不同线程拿到的牌组数据混乱。
修复方案
1. 替换线程不安全集合
将用于存储计算结果的Dictionary替换为.NET 自带的线程安全字典 ConcurrentDictionary<TKey, TValue>,使用原子操作TryAdd替代ContainsKey+Add的非安全组合:
// 替换原有Dictionary定义 ConcurrentDictionary<FlopEquityHoldemHandPair, FlopEquityHoldemHandPair> flopPairs = new(); // 替换原有判断+添加逻辑 FlopEquityHoldemHandPair holdemHandPair = new FlopEquityHoldemHandPair(hand1, hand2); flopPairs.TryAdd(holdemHandPair, new FlopEquityHoldemHandPair(new EquityHoldemHand(hand1), new EquityHoldemHand(hand2), flop));
2. 消除共享变量,改为线程局部声明
不要在并行循环外部声明deck变量,直接在每个flop的处理逻辑内部独立声明牌组,避免跨线程修改:
// make list of all available starting hands List<HoldemHand> hands = new List<HoldemHand>(); CardSet deck = new CardSet(); // 内部独立声明,不使用外层共享的deck deck.RemoveAll(flop);
3. 进度更新加锁
多线程同时修改progress进度变量时需要加锁,避免计数错误:
object progressLock = new object(); // ... int num = ((int)(i * 100 / 22100)); lock (progressLock) { if (num > progress) { progress = num; Console.WriteLine("Progress: " + progress + "%"); } }
4. 嵌套并行的性能提示
如果你同时开启外层flop循环和内层手牌循环的并行,可能会出现线程过度调度反而降低性能的问题,建议只保留外层并行,或者通过ParallelOptions限制最大并行度匹配CPU核心数。
内容的提问来源于stack exchange,提问作者Steve W
相关产品推荐
相关产品推荐

