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

如何正确用IMemoryCache缓存Task<T>?WCF服务缓存封装问题

解决WCF服务缓存Task的失败缓存问题

我之前在封装WCF远程服务调用的缓存层时,完全踩过你说的这个坑——直接缓存Task<int[]>的话,第一次调用失败的错误任务会被死死存在缓存里,后面所有请求拿Result的时候都会抛出同一个异常,除非缓存过期或者手动清掉,简直头疼。

后来我重构了缓存逻辑,核心就是放弃缓存Task对象,转而缓存实际的业务数据,同时处理好并发请求的问题,具体思路是这样的:

  • 缓存实际结果而非任务:把int[]这类最终数据存在缓存里,而不是包裹它的Task。但这里要注意,不能让多个并发请求同时触发WCF调用(也就是缓存击穿),得加个“正在加载”的标记来协调。
  • 用TaskCompletionSource做并发占位符:当第一个请求发起时,先在缓存里放一个TaskCompletionSource<int[]>作为占位,表示“我正在拿数据,你们等我”。后续请求看到这个占位符,就直接await它的Task,不用再去调用WCF。
  • 失败时不缓存任何内容:如果WCF调用失败,直接把缓存里的占位符删掉,这样下次请求会重新尝试调用,不会一直拿到错误的任务。

给你贴一段我当时写的核心代码示例,你可以参考下:

private readonly ConcurrentDictionary<string, object> _cacheStore = new ConcurrentDictionary<string, object>();
private readonly SemaphoreSlim _asyncLock = new SemaphoreSlim(1, 1);

public async Task<int[]> FetchRemoteDataAsync(string cacheKey)
{
    // 先尝试获取缓存的结果
    if (_cacheStore.TryGetValue(cacheKey, out var cachedItem) && cachedItem is int[] dataResult)
    {
        return dataResult;
    }

    // 检查是否有请求正在加载数据,有的话直接等它完成
    if (_cacheStore.TryGetValue(cacheKey, out var loadingMarker) && loadingMarker is TaskCompletionSource<int[]> loadingTcs)
    {
        return await loadingTcs.Task;
    }

    // 加锁做双重检查,避免并发创建多个占位符
    using (await _asyncLock.WaitAsync())
    {
        // 再次检查缓存,防止加锁期间已经有请求完成了
        if (_cacheStore.TryGetValue(cacheKey, out var recheckItem) && recheckItem is int[] recheckResult)
        {
            return recheckResult;
        }
        if (_cacheStore.TryGetValue(cacheKey, out var recheckMarker) && recheckMarker is TaskCompletionSource<int[]> recheckTcs)
        {
            return await recheckTcs.Task;
        }

        // 创建新的加载占位符并存入缓存
        var loadTcs = new TaskCompletionSource<int[]>();
        _cacheStore.TryAdd(cacheKey, loadTcs);

        try
        {
            // 实际调用WCF远程服务
            var remoteData = await CallWcfServiceAsync();
            // 用实际结果替换缓存里的占位符
            _cacheStore.TryUpdate(cacheKey, remoteData, loadTcs);
            return remoteData;
        }
        catch (Exception ex)
        {
            // 调用失败,移除占位符,不缓存错误
            _cacheStore.TryRemove(cacheKey, out _);
            // 把异常抛出去,让上层处理
            throw;
        }
    }
}

这段代码的逻辑很清晰:用ConcurrentDictionary做缓存容器,SemaphoreSlim处理异步锁,TaskCompletionSource协调并发请求。成功就把实际数据放进缓存,失败就清空占位符,完美解决了错误任务被缓存的问题,还避免了缓存击穿。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 07:50:59