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

.NET实现文件缓存:选用lock加Dictionary还是ConcurrentDictionary?

问题根因

你遇到的重复读取问题是ConcurrentDictionary的GetOrAdd方法的设计导致的:传入的工厂委托并不在字典的内部锁保护范围内。当多个线程同时查询同一个不存在的key时,每个线程都会独立执行工厂方法,直到其中一个线程完成写入后,后续的线程才会直接取到已存在的值,因此会出现多次读文件的情况。

最优解决方案:ConcurrentDictionary + Lazy

这个方案既保留了ConcurrentDictionary细粒度锁的优势,又能保证文件读取逻辑仅执行一次,不会出现大范围阻塞。核心思路是把字典的值包装为Lazy<byte[]>,利用Lazy本身的线程安全特性保证耗时的文件读取仅执行一次:

class Program
{
    private static ConcurrentDictionary<string, Lazy<byte[]>> cache;

    static void Main(string[] args)
    {
        cache = new ConcurrentDictionary<string, Lazy<byte[]>>();

        Task.Run(() =>
        {
            AddToCache("largefile", "largefile.bin");
        });

        Task.Run(() =>
        {
            AddToCache("largefile", "largefile.bin");
        });
    }

    static byte[] AddToCache(string key, string filename)
    {
        var lazyData = cache.GetOrAdd(key, (s) => 
            new Lazy<byte[]>(() => 
            {
                Console.WriteLine("Reading file into cache");
                return File.ReadAllBytes(filename); 
            }, LazyThreadSafetyMode.ExecutionAndPublication)
        );
        return lazyData.Value;
    }
}

方案原理

  • 字典的写入操作仅创建轻量的Lazy<byte[]>实例,就算多个线程同时调用GetOrAdd,创建多个Lazy实例的开销也可以忽略不计。
  • LazyThreadSafetyMode.ExecutionAndPublication保证Lazy实例的工厂方法(也就是读文件的逻辑)仅会执行一次。
  • 最终所有线程拿到的都是字典中存储的同一个Lazy实例,读取文件的操作只会触发一次。

方案优势对比

方案优点缺点
全局lock+Dictionary实现简单,保证读文件仅一次锁粒度太大,所有key的读写都争抢同一把锁,高并发下吞吐量低
原生ConcurrentDictionary细粒度锁,吞吐量高同key并发查询时会多次执行耗时的工厂逻辑
ConcurrentDictionary+Lazy细粒度锁,吞吐量高,保证读文件仅一次多了一层Lazy包装的极小开销

异步场景适配

如果你的文件读取是异步实现,只需要把值改成Lazy<Task<byte[]>>即可:

static async Task<byte[]> AddToCacheAsync(string key, string filename)
{
    var lazyTask = cache.GetOrAdd(key, (s) => 
        new Lazy<Task<byte[]>>(async () => 
        {
            Console.WriteLine("Reading file into cache");
            return await File.ReadAllBytesAsync(filename); 
        }, LazyThreadSafetyMode.ExecutionAndPublication)
    );
    return await lazyTask.Value;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 01:57:04