多线程场景下删除数组索引0元素抛出IndexOutOfRangeException但数组非空
问题原因与修复方案
问题根源
- 竞态条件导致的非原子操作:你在判断
BigArray.Count != 0和执行BigArray.RemoveAt(0)之间存在时间间隙,其他线程完全可能在这段时间里删掉最后一个元素,等当前线程执行RemoveAt时数组已经为空,直接触发索引越界异常。 - 集合本身非线程安全:如果
BigArray是普通的List<T>这类集合,它根本不支持多线程并发读写。就算用了AutoResetEvent,也没保护集合的访问操作,多个线程同时读写必然导致状态混乱。 - 同步逻辑没管住集合修改:当前的
AutoResetEvent只是协调了线程的等待和唤醒,但没控制住多个线程同时删除元素的行为。比如多个线程都判断Count>0,然后先后执行删除,最后一个线程执行时数组已经空了。
修复方案
1. 给所有集合操作加锁
把所有读写BigArray的代码(包括Count判断、取元素、RemoveAt)都放到同一个锁块里,确保这些操作是原子性的,不会被其他线程打断:
// 定义全局锁对象 private readonly object _arrayLock = new object(); // 线程逻辑中取元素的部分修改: lock (_arrayLock) { if (BigArray.Count < index + 1) { Debug.WriteLine("BigArray is smaller than " + (index + 1)); continue; } byte[] data = BigArray[index]; } // 删除元素的部分修改: lock (_arrayLock) { if (BigArray.Count != 0) BigArray.RemoveAt(0); }
2. 改用线程安全集合
如果场景允许,直接用.NET内置的ConcurrentQueue<T>代替普通集合。它的TryDequeue方法本身就是线程安全的,能直接避免手动加锁的麻烦,还能保证元素只被处理一次:
// 替换原来的BigArray private ConcurrentQueue<byte[]> _dataQueue = new ConcurrentQueue<byte[]>(); // 线程里的处理逻辑可改为: if (_dataQueue.TryDequeue(out byte[] data)) { Processing(data); // 其他处理逻辑 }
3. 重新梳理线程同步逻辑
当前依赖线程索引的AutoResetEvent同步逻辑有问题,没正确协调集合修改的节奏。如果必须保留按索引处理的逻辑,得确保同一时间只有一个线程能执行删除操作,并且删除后通知其他线程重新检查集合状态。比如用Monitor.Wait和Monitor.Pulse来替代AutoResetEvent,更灵活地控制线程等待集合有足够元素的场景。
额外提醒
- 永久循环的线程一定要加退出条件,比如用
CancellationToken,否则程序退出时线程可能无法正常终止。 - 当前逻辑里,线程按索引取元素的方式可能导致重复处理或处理已被删除的元素,加锁或改用线程安全集合后要重新验证这部分逻辑。
内容的提问来源于stack exchange,提问作者Tibin Saji
相关产品推荐
相关产品推荐

