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

ASP.NET HostedService中单例List元素读写是否需要考虑线程安全

问题结论

你遇到的场景两个层面都存在线程安全问题,Foo.Value的同步和FooManager.FooList的同步不是二选一的关系,需要分别处理。

1. 关于FooManager.FooList的线程安全

你已知List本身的新增、删除、遍历操作不是线程安全的,结合你的代码场景,风险点非常明确:

  • FooUpdater作为后台托管服务,会永久循环遍历FooList,如果此时FooController的多线程请求对FooList执行Add/Remove操作,或者直接给FooList属性赋新实例,会直接触发InvalidOperationException(集合被修改异常),导致后台服务直接崩溃。
  • 即便没有触发异常,并发修改下的List遍历也可能出现元素重复读取、遗漏读取、甚至内存损坏的问题。

处理方案

  • 首先修改FooManager的FooList属性,将setter改为私有,禁止外部直接替换列表实例:
public List<Foo> FooList {get; private set;}
  • 在FooManager内部定义全局锁对象,所有对FooList的操作(新增、删除、遍历、清空)都要持有这把锁:
public class FooManager // Singleton
{
    private readonly object _listLock = new object();
    public List<Foo> FooList {get; private set;}
  
    public FooManager()
    {
        FooList = new List<Foo>();
    }

    // 封装Add操作,内部加锁
    public void AddFoo(Foo foo)
    {
        lock(_listLock)
        {
            FooList.Add(foo);
        }
    }

    // 封装获取遍历快照的方法,避免外部直接遍历原始集合
    public List<Foo> GetFooSnapshot()
    {
        lock(_listLock)
        {
            return FooList.ToList();
        }
    }
}
  • 如果你用的是.NET Core 2.0+版本,也可以直接使用内置的线程安全集合(如ConcurrentBag<Foo>),它的遍历是快照式的,不会抛出集合修改异常,但要注意你拿到的是遍历启动那一刻的集合状态,需要符合你的业务预期。

2. 关于Foo.Value的线程安全

就算FooList的操作已经完全线程安全,Foo.Value的读写依然存在并发风险:

  • FooUpdater的UpdateValue方法在等待10秒后会给Value赋值,同时FooController的多线程请求可能正在读写同一个Foo实例的Value,会出现竞态条件:比如读操作拿到未完成更新的脏数据、多个写操作互相覆盖导致最终结果不符合预期。
  • 注意虽然string的引用赋值本身是原子操作,但可见性无法保证(CPU缓存没有同步的话,读线程可能长时间看不到新赋值的Value),如果涉及判断后写入、字符串拼接等非原子操作,风险会更高。

处理方案

不要为了图方便直接锁整个FooList,那样会严重降低并发性能,推荐在Foo内部单独加锁保护Value的所有读写操作:

public class Foo
{
    private readonly object _valueLock = new object();
    private string _value;
    public string Value 
    {
        get 
        {
            lock(_valueLock) return _value;
        }
        set 
        {
            lock(_valueLock) _value = value;
        }
    }

    public async Task UpdateValue(string value)
    {
        await Task.Delay(10000);
        Value = value;
    }
}

所有对Value的访问都走封装好的带锁属性即可保证线程安全。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 18:15:10