ConcurrentBag.Add() 线程安全但遍历时可能漏数据因其采用线程本地栈全局池设计遍历仅反映调用瞬间可见状态正确遍历应使用 TryTake() 持续消费而非 foreach 或 ToArray()。ConcurrentBag.Add() 看似安全但遍历时可能漏数据ConcurrentBag 是线程安全的添加/取出容器但它的设计目标不是“强一致性遍历”——foreach 或 ToArray() 得到的快照不保证包含所有已 Add() 的元素尤其在高并发写入时。这是因为它的内部实现用的是每个线程私有的本地栈thread-local stack新元素先压入本地栈只有当本地栈空了、或发生线程迁移时才把部分元素“偷”到全局池。遍历时只读取当前可见的本地栈 全局池而其他线程的本地栈可能还没被合并。适用场景大量线程频繁 Add()后续统一消费比如日志缓冲、事件暂存不依赖“实时全量可见”不适用场景需要严格按插入顺序遍历、或要求遍历时看到“所有已添加项”的逻辑如状态聚合、校验避免用 Count 判断是否为空——它只是近似值可能返回 0 即使还有待合并的本地元素遍历 ConcurrentBag 的正确姿势别直接 foreach直接 foreach (var item in bag) 会触发内部快照逻辑结果不可靠。真正安全的遍历方式是“消费式取出”即用 TryTake() 持续取直到失败这能确保拿到所有已归并的元素包括从其他线程本地栈迁移来的。如果必须转成数组再处理用 bag.ToArray() 比 foreach 稍稳但它仍不保证包含“刚刚 Add 的那个”仅反映调用瞬间的合并状态若需强一致性改用 ConcurrentQueuet/tFIFO 保证或加锁保护普通 Listt/t别硬扛 ConcurrentBag 的语义边界TryPeek() 只看不取同样受快照限制不能用于判断“是否存在某类元素”ConcurrentBag 的性能陷阱Add() 快但内存占用随线程数线性涨每个线程第一次操作 ConcurrentBag 时都会分配一个独立的本地栈默认初始容量 32。线程越多未释放的本地栈越多即使线程退出这些栈对象也不会自动回收除非 ConcurrentBag 被 GC —— 它不实现 IDisposable也没有清理 API。 Zeemo AI 一款专业的视频字幕制作和视频处理工具