我的异步Lambda用法在缓存旁模式中正确吗?

时间:2019-06-16 10:31:09

标签: c# async-await

我对异步和Lambda函数有点困惑。

我有一个旧的CacheHelper函数(基本上是一个缓存预留模式),它可以执行以下操作:

public static T GetOrAdd<T>(Func<Task<T>> builder, TimeSpan expiresIn, bool ignoreNullValues, params string[] keys)
{
      ////check if the cache item is available
       xxxx
      //// if not, call the builder function to get fresh
      var item = builder().Result;
      //// add to cache and return the item
      return item;
}

现在我们正在转向异步模式,所以这就是我调用缓存助手的方式:

    CacheAsideHelper.GetOrAdd(
              async () => await _currencyRepository.GetCurrencyInfo(currencyCode, commandTimeout, taskTimeout, _trackingId),
                    new TimeSpan(Constants.ExpirationDays, 0, 0), true, key);

我运行了一些测试,似乎可以预期结果。但是我的一位同事说,自从我传递异步lamda以来,我的缓存有时可能包含Task<T>,而不是对象T。所以我需要以某种方式等待它。

但是,我的测试似乎也可以在我的Cache helper代码中提供正确的数据

var item = builder().Result;

缓存将始终包含真实数据(Task<T>除外),我是对的还是我的同事是正确的?

1 个答案:

答案 0 :(得分:0)

实际上可以缓存Task本身而不是结果,请参见https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/

  

作为一个类的任务非常灵活,并因此而受益。对于   例如,您可以由任意数量的消费者多次等待   同时。您可以将一个字典存储为任意数量的字典   随后的消费者在将来等待,这使得它成为   用作异步结果的缓存。

拥有一项任务(完成或未完成),您可以根据需要等待多次。因此,您可以执行以下操作:

public static Task<T> GetOrAdd<T>(Func<Task<T>> builder, string key, TimeSpan expiresIn)
{
      //1. try find task in cache
      //2. if task is failed or cancelled - run builder and put task into cache
      //3. otherwise just return the task
}

仍然存在两个问题:

  1. builder()中的异常处理
  2. 并发初始化:仍然可以从不同的线程运行builder()几次

在这里您可以找到解决这两个问题的可能方法:https://github.com/MaximTkachenko/cache-once/blob/master/src/Mtk.CacheOnce/MemoryCacheOnceExtensions.cs