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

双线程访问同一List触发System.ArgumentOutOfRangeException该如何处理?

异常根本原因

你碰到的问题本质由两个原因导致:

  1. List<T>属于非线程安全类型,跨线程同时读写没有同步保护时,除了索引越界,还可能出现集合状态损坏、脏数据读取等问题
  2. 没有做执行时序同步,T2执行读操作的时机早于T1完成所有元素写入的时机,此时集合为空,访问索引0自然抛出越界异常

不同场景的解决方案

场景1:T1必须完成所有元素添加后,T2才允许访问集合

这是最贴合你问题描述的场景,推荐用ManualResetEventSlim做轻量级时序同步:

private readonly List<item> myCompletedItems = new List<item>();
// 初始化信号为未触发状态
private readonly ManualResetEventSlim _addCompletedSignal = new ManualResetEventSlim(false);

T1所有元素添加完成后触发信号:

// T1执行的批量添加逻辑
public void AddAllItems(List<item> itemsToAdd)
{
    foreach (var item in itemsToAdd)
    {
        myCompletedItems.Add(item);
    }
    // 所有元素添加完成,标记信号为已触发
    _addCompletedSignal.Set();
}

T2调用的读方法先等待信号触发再访问集合:

public int GetStartItemId()
{          
    // 等待T1完成添加,也可以传入超时参数避免无限阻塞
    _addCompletedSignal.Wait();
    return myCompletedItems[0].id;                
}

如果提前知道T1要添加的元素总数,也可以用CountdownEvent,每添加1个元素调用Signal()减计数,T2等待计数归0后再访问即可。


场景2:不需要等所有元素加完,T2只要保证读时集合非空、操作线程安全

如果业务允许T1加一个T2就读一个,不需要等全部添加完成,直接用.NET内置的线程安全集合BlockingCollection<T>即可,天然支持阻塞读直到有元素可用:

// 底层默认用ConcurrentQueue<T>存储,也可以指定其他线程安全集合
private readonly BlockingCollection<item> myCompletedItems = new BlockingCollection<item>();

添加逻辑基本无需修改:

public void ItemCreated(item theitem)
{
    myCompletedItems.Add(theitem);
    // 所有元素添加完成后调用该方法,后续读操作不会再阻塞等待新元素
    // myCompletedItems.CompleteAdding();
}

读方法自动阻塞直到有元素可取:

public int GetStartItemId()
{          
    var firstItem = myCompletedItems.Take();
    return firstItem.id;                
}

该方案不需要手动写同步逻辑,出错概率低,非常适合生产者-消费者场景。


场景3:不想修改原有集合定义,需要兼容旧逻辑

如果不想改动现有集合类型,用lock加完成标志位也可以实现需求:

private readonly List<item> myCompletedItems = new List<item>();
private readonly object _lockObj = new object();
private bool _isAddCompleted = false;

添加完成后修改标志位唤醒读线程:

public void AddAllItems(List<item> itemsToAdd)
{
    lock (_lockObj)
    {
        foreach (var item in itemsToAdd)
        {
            myCompletedItems.Add(item);
        }
        _isAddCompleted = true;
        Monitor.PulseAll(_lockObj);
    }
}

读方法循环等待添加完成:

public int GetStartItemId()
{          
    lock (_lockObj)
    {
        // 循环等待避免虚假唤醒
        while (!_isAddCompleted)
        {
            Monitor.Wait(_lockObj);
        }
        return myCompletedItems[0].id;
    }             
}

注意事项

  • 任何场景下都不要无同步跨线程访问非线程安全集合,除了索引越界,还可能触发更难排查的隐式集合损坏问题
  • 阻塞等待方法建议传入超时参数,避免业务异常时线程无限挂起

内容的提问来源于stack exchange,提问作者Mr.Curious

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 22:24:01