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

缓存行星数据的服务长时间闲置后触发IndexOutOfRangeException求助

问题分析

你的PlanetService存在线程安全问题,这是触发IndexOutOfRangeException的核心原因:

  • List<T>并非线程安全集合,当多个线程同时执行FindAll或FindByName时,可能出现一个线程在执行planets.AddRange(修改集合内部结构),另一个线程同时枚举planets(比如调用Any()、Where或Select)的冲突场景,这会破坏List<T>的内部数组结构,引发索引越界异常。
  • 异常触发后,List<T>的内部状态会处于损坏状态,后续所有访问都会持续报错,直到应用重启或缓存被重置。
  • 生产环境的并发访问场景是问题触发的关键,开发环境因并发量低难以复现。
修复方案

以下是几种可行的修复方式,按推荐程度排序:

1. 使用Lazy<T>实现线程安全的惰性初始化

Lazy<T>默认具备线程安全性,能确保缓存仅被初始化一次,彻底避免并发修改/枚举的冲突:

public class PlanetService
{
    private readonly Lazy<List<Planet>> _planets = new Lazy<List<Planet>>(() => GetAllPlanetsFromDataBase().ToList());

    public List<Planet> FindAll()
    {
        return _planets.Value;
    }

    public List<Planet> FindByName(string name)
    {
        return _planets.Value.Where(x => x.Name == name).ToList();
    }

    // 假设GetAllPlanetsFromDataBase是返回IEnumerable<Planet>的数据库查询方法
    private static IEnumerable<Planet> GetAllPlanetsFromDataBase()
    {
        // 数据库查询逻辑
    }
}

2. 加锁实现线程安全的缓存初始化

通过lock关键字确保缓存初始化过程的原子性,避免并发冲突:

public class PlanetService
{
    private List<Planet> _planets = new List<Planet>();
    private readonly object _lockObj = new object();

    public List<Planet> FindAll()
    {
        if (!_planets.Any())
        {
            lock (_lockObj)
            {
                // 双重检查锁定,避免重复初始化
                if (!_planets.Any())
                {
                    _planets.AddRange(GetAllPlanetsFromDataBase());
                }
            }
        }
        return _planets;
    }

    public List<Planet> FindByName(string name)
    {
        if (!_planets.Any())
        {
            lock (_lockObj)
            {
                if (!_planets.Any())
                {
                    _planets.AddRange(GetAllPlanetsFromDataBase());
                }
            }
        }
        return _planets.Where(x => x.Name == name).ToList();
    }

    private static IEnumerable<Planet> GetAllPlanetsFromDataBase()
    {
        // 数据库查询逻辑
    }
}

3. 使用线程安全集合替换List<T>

如果需要支持缓存的动态更新(当前场景不需要,但可扩展),可以使用ConcurrentBag<T>或ConcurrentDictionary<TKey, TValue>:

public class PlanetService
{
    private readonly ConcurrentBag<Planet> _planets = new ConcurrentBag<Planet>();
    private readonly object _lockObj = new object();

    public IEnumerable<Planet> FindAll()
    {
        if (_planets.IsEmpty)
        {
            lock (_lockObj)
            {
                if (_planets.IsEmpty)
                {
                    foreach (var planet in GetAllPlanetsFromDataBase())
                    {
                        _planets.Add(planet);
                    }
                }
            }
        }
        return _planets;
    }

    public IEnumerable<Planet> FindByName(string name)
    {
        return FindAll().Where(x => x.Name == name);
    }

    private static IEnumerable<Planet> GetAllPlanetsFromDataBase()
    {
        // 数据库查询逻辑
    }
}
补充说明
  • 无论采用哪种方案,需确保GetAllPlanetsFromDataBase方法本身线程安全(通常数据库查询方法是安全的,但要避免共享数据库连接等问题)。
  • 如果需要定期刷新缓存,可以在方案中添加过期逻辑,比如记录缓存初始化时间,每次访问时检查是否需要重新加载。

内容的提问来源于stack exchange,提问作者FLash

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 12:05:22