You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 15:19:59