List.Remove()抛出索引越界异常问题排查与解决方案咨询
我有两个列表:List<Customer> 和 List<ThreadModified>。创建新的ThreadModified对象时会同时创建对应的Customer对象,并将ThreadModified的引用关联到Customer实例中。在Customer类的CustomerLeaves()方法里,我尝试用List<>.Remove()把Customer和ThreadModified从各自的列表中移除。代码刚开始运行正常,但处理几百个Customer后,抛出了**"Index was out of range. Must be non-negative and less than the size of the collection. Arg_ParamName_Name"**异常。另外,我试过用List<>.Find()方法,但处理几百个Customer后该方法会返回null。想请教:是否应该把移除列表元素的操作放在Customer类外部执行?
相关代码
ThreadModified 类
public class ThreadModified { public int thread_id; public Thread thread; }
Customer 类
public class Customer { public required Store store; static Random rnd = new Random(); public required ThreadModified thread; public int customer_id; public int numberOfProducts = rnd.Next(1, 20); public bool served = false; public bool unsatisfied = false; object locker = new(); int queueLengthTolerance = rnd.Next(5, 15); public void CustomerShopping() { for (int i = 0; i < numberOfProducts; i++) { Thread.Sleep(rnd.Next(1, 3) / MainWindow.timeModifier); } CustomerGetInLine(); } // 其他方法... public void CustomerLeaves() { lock (locker) { if (served) { store.customersThreads.Remove(thread); // 此处抛出异常 store.customers.Remove(this); // 此处抛出异常 store.customersServed++; store.customersLeft++; store.customersInside--; } if (unsatisfied) { store.customersThreads.Remove(thread); // 此处抛出异常 store.customers.Remove(this); // 此处抛出异常 store.customersUnsatisfied++; store.customersLeft++; store.customersInside--; } } } }
Store 类(创建Customer与ThreadModified的逻辑)
public class Store { static Random rnd = new Random(); public int store_id; public List<ThreadModified> customersThreads; public List<ThreadModified> cashiersThreads; object locker = new(); public List<Customer> customers; public List<Cashier> cashiers; public int maxCustomersInside = 10000; int maxCashiers = 5; public int customersInside = 0; public int customersEntered = 0; public int customersLeft = 0; public int customersUnsatisfied = 0; public int customersServed = 0; public void CustomersBehavior(object id_object) { int id = (int)id_object; //Customer customer = customers.Find(cust => cust.customer_id == id); Customer customer = customers[^1]; customer.CustomerShopping(); } // 创建新Customer public void NewCustomerEnters() { customersInside++; customersEntered++; customersThreads.Add(new ThreadModified() { thread_id = customersEntered ,thread = new Thread(new ParameterizedThreadStart(CustomersBehavior)) { Name = $"Customer id – {customersEntered}" } }); customers.Add(new Customer() { customer_id = customersEntered, store = this, thread = customersThreads[^1] }); customersThreads[^1].thread.Start((object)customersEntered); Thread.Sleep(rnd.Next(5, 50) / MainWindow.timeModifier); while (customersThreads.Count < maxCustomersInside && customersLeft < 5000) { NewCustomerEnters(); } } public void ServeCustomers(object id_object) { int id = (int)id_object; Cashier cashier = cashiers.Find(cash => cash.cashier_id == id); cashier.ServeQueue(); } public void NewCashiers() { cashiers.Add(new Cashier() { cashier_id = cashiers.Count + 1 }); } }
问题根源与解决方案
1. 异常和Find返回null的核心原因
- 线程安全问题:
List<T>不是线程安全的集合,当多个线程同时对customers或customersThreads进行添加/移除操作时,会导致集合内部结构损坏,出现索引越界或查找失败的情况。 - 锁的范围错误:
Customer类里的locker是每个实例单独的对象,无法阻止多个Customer线程同时修改Store里的共享列表。 - CustomersBehavior的逻辑错误:用
customers[^1]获取当前Customer是完全错误的——当多个线程同时执行NewCustomerEnters时,customers[^1]会拿到最新添加的Customer,而非当前线程对应的实例,导致后续逻辑混乱,甚至重复移除同一个元素。
2. 正确的实现思路
(1)改用线程安全集合
把List<Customer>和List<ThreadModified>换成ConcurrentDictionary<TKey, TValue>,通过customer_id或thread_id作为键,能更安全地进行查找和移除操作:
// Store类里修改集合类型 public ConcurrentDictionary<int, Customer> customers = new ConcurrentDictionary<int, Customer>(); public ConcurrentDictionary<int, ThreadModified> customersThreads = new ConcurrentDictionary<int, ThreadModified>();
(2)集中管理移除操作(推荐在Store类中统一处理)
把移除逻辑移到Store类中,用Store的全局锁保护共享集合的操作,避免多个线程同时修改:
// 在Store类中添加移除方法 public void RemoveCustomer(Customer customer) { lock (locker) { if (customer.served) { customersThreads.TryRemove(customer.thread.thread_id, out _); customers.TryRemove(customer.customer_id, out _); customersServed++; customersLeft++; customersInside--; } else if (customer.unsatisfied) { customersThreads.TryRemove(customer.thread.thread_id, out _); customers.TryRemove(customer.customer_id, out _); customersUnsatisfied++; customersLeft++; customersInside--; } } }
然后修改Customer的CustomerLeaves方法:
public void CustomerLeaves() { store.RemoveCustomer(this); }
(3)修复CustomersBehavior的逻辑错误
不要用customers[^1],而是通过customer_id从线程安全集合中查找对应的Customer:
public void CustomersBehavior(object id_object) { int id = (int)id_object; if (customers.TryGetValue(id, out Customer customer)) { customer.CustomerShopping(); } }
3. 关于是否在Customer外部执行移除操作
是的,非常推荐。因为Store是所有共享集合的持有者,把集合修改逻辑集中在Store类中,能更好地控制线程安全,避免多个实例各自操作共享资源导致的竞争问题。这样也符合单一职责原则:Customer只负责自身的行为逻辑,Store负责管理所有Customer和线程的生命周期。
内容的提问来源于stack exchange,提问作者BrainButter

