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

ASP.NET Web API长耗时请求如何避免重复调用并实现服务端缓存?

嘿,你的思路完全没问题——用ConcurrentDictionary跟踪正在运行的请求任务,这是解决缓存击穿(冷启动时并发请求同一资源)的经典方案之一!不过你的代码确实可以优化得更简洁、更安全,我来给你捋捋:

首先,原代码里先ContainsKey再赋值的逻辑存在竞态条件:两个线程可能同时通过ContainsKey的判断,导致重复创建Task。另外,存储Task而非Task<T>需要强制类型转换,既不安全也不优雅。

这里给你两个优化方向,都是更简洁且线程安全的实现:


方案一:用ConcurrentDictionary.GetOrAdd原子化管理Task

这个方案直接优化你原有的Task思路,消除竞态,代码更紧凑:

private static readonly ConcurrentDictionary<string, Task<T>> _pendingTasks = new ConcurrentDictionary<string, Task<T>>();

public static T GetFromCache<T>(this ICacheManager<object> cacheManager, string name, Func<T> func)
{
    // 优先检查缓存,命中直接返回
    if (cacheManager.Exists(name))
        return (T)cacheManager[name];

    // 原子性获取或创建任务:确保同一个key只会生成一个Task
    var runningTask = _pendingTasks.GetOrAdd(name, _ => Task.Run(func));

    try
    {
        // 等待任务完成,获取结果
        var result = runningTask.Result;
        // 将结果存入缓存
        cacheManager.Put(name, result);
        return result;
    }
    finally
    {
        // 无论任务成功/失败,都清理pending任务,避免后续请求等待失效任务
        _pendingTasks.TryRemove(name, out _);
    }
}

优化点说明:

  • 用GetOrAdd代替先判断再赋值,彻底消除了多线程下的竞态问题
  • 直接存储Task<T>,不需要类型转换,类型更安全
  • 把任务清理放在finally块里,确保无论执行结果如何,都不会留下无效的任务占用资源

方案二:用Lazy<T>简化线程安全初始化

如果你的func是纯同步的长耗时操作,用Lazy<T>会更简洁——它本身就内置了线程安全的初始化逻辑,确保只有一个线程执行初始化代码:

private static readonly ConcurrentDictionary<string, Lazy<T>> _lazyInitializers = new ConcurrentDictionary<string, Lazy<T>>();

public static T GetFromCache<T>(this ICacheManager<object> cacheManager, string name, Func<T> func)
{
    if (cacheManager.Exists(name))
        return (T)cacheManager[name];

    // 原子性获取或创建Lazy实例,确保同一key只有一个初始化逻辑
    var lazyLoader = _lazyInitializers.GetOrAdd(name, _ => 
        new Lazy<T>(func, LazyThreadSafetyMode.ExecutionAndPublication));

    try
    {
        var result = lazyLoader.Value;
        cacheManager.Put(name, result);
        return result;
    }
    finally
    {
        _lazyInitializers.TryRemove(name, out _);
    }
}

优势:

  • 代码更简洁,不需要手动管理Task的生命周期
  • LazyThreadSafetyMode.ExecutionAndPublication模式会自动让所有等待线程共享第一个线程的初始化结果,完全符合你的需求

两种方案都能很好解决你的问题,选哪个取决于你的场景:如果需要支持异步操作(比如func是Func<Task<T>>),方案一更适合;如果是纯同步场景,方案二更简洁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:35:29