使用 IMemoryCache 缓存结果 Task<T> 的正确方法

Posted

技术标签:

【中文标题】使用 IMemoryCache 缓存结果 Task<T> 的正确方法【英文标题】:Proper way to cache results Task<T> with IMemoryCache 【发布时间】:2018-07-22 23:02:57 【问题描述】:

我正在寻找一种方法来包装一个为某些数据调用远程 WCF 服务的服务。例如,此类服务可能如下所示

public interface IAsyncSvc

    Task<int[]> GetData(int key);


public class SomeAsyncSvc:IAsyncSvc

    public Task<int[]> GetData(int key)
    
        Console.WriteLine("SomeAsyncSvc::GetData()");
        return Task.Factory.StartNew(() =>
        
            //Some time-consuming operation
            Thread.Sleep(1000);
            return Enumerable.Range(1, key).ToArray();
        );
    

我第一次写了一个简单的缓存包装器:

public class SomeAsyncSvcCachedTask : IAsyncSvcCached

    private readonly IAsyncSvc _svc;
    private readonly IMemoryCache _cache;

    public SomeAsyncSvcCachedTask(IAsyncSvc svc, IMemoryCache cache)
    
        _svc = svc;
        _cache = cache;
    

    public Task<int[]> GetData(int v)
    
        if (_cache.TryGetValue(v, out Task<int[]> cacheEntry))
            return cacheEntry;

        var task = _svc.GetData(v);
        var cacheEntryOptions = new MemoryCacheEntryOptions()
            .SetAbsoluteExpiration(TimeSpan.FromSeconds(5));

        _cache.Set(v, task, cacheEntryOptions);

        return task;
    

缓存Task的主要缺点是,如果第一次尝试缓存失败,我会缓存一个出错的任务,并一遍又一遍地查询Task.Result,直到另一个未缓存的成功调用。

然后我为它写了另一个包装器:

public class SomeAsyncSvcCached : IAsyncSvcCached

    private readonly IAsyncSvc _svc;
    private readonly IMemoryCache _cache;

    public SomeAsyncSvcCached(IAsyncSvc svc, IMemoryCache cache)
    
        _svc = svc;
        _cache = cache;
    

    public Task<int[]> GetData(int v)
    
        if (_cache.TryGetValue(v, out int[] cacheEntry))
            return Task.FromResult(cacheEntry);

        var task = _svc.GetData(v);
        task.ContinueWith(t =>
        
            var cacheEntryOptions = new MemoryCacheEntryOptions()
                .SetAbsoluteExpiration(TimeSpan.FromSeconds(5));

            _cache.Set(v, t.Result, cacheEntryOptions);
        , TaskContinuationOptions.NotOnFaulted);

        return task;
    

主要思想不是缓存Task&lt;int[]&gt;,而只是缓存int[]类型的结果。作为一个优势,如果第一次调用失败,那么这个包装器会尝试一遍又一遍地读取数据,而不是返回缓存的错误。

这种方法有什么缺陷?也许有一种更简单的方法来实现缓存返回Task&lt;&gt;的方法调用的目标?

【问题讨论】:

【参考方案1】:

看一下文章: http://cpratt.co/thread-safe-strongly-typed-memory-caching-c-sharp/

public static async Task<T> AddOrGetExistingAsync<T>(
    this ObjectCache cache,
    string key,
    Func<Task<T>> valueFactory,
    CacheItemPolicy policy)

    var newValue = new AsyncLazy<T>(valueFactory);
    var oldValue = cache.AddOrGetExisting(key, newValue, policy) as Lazy<T>;

    try
    
        return oldValue != null ? oldValue.Value : await newValue.Value;
    
    catch
    
        cache.Remove(key);
        throw;
    

【讨论】:

以上是关于使用 IMemoryCache 缓存结果 Task<T> 的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

在ASP.NET Core应用中使用IMemoryCache缓存

在ASP.NET Core应用中使用IMemoryCache缓存

在ASP.NET Core应用中使用IMemoryCache缓存

在ASP.NET Core应用中使用IMemoryCache缓存

在 TEST IIS 机器上使用 C# IMemoryCache 进行永久缓存不起作用

Azure在内存(IMemoryCache)或Redis(IDistributedCache)中使用的缓存模型