我的异步 lambda 用法在缓存端模式中是否正确?



我对异步和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,我的缓存有时可能包含对象 T 之外的Task<T>。所以不知何故我需要等待它。

但是,我的测试似乎给出了正确的数据,也在我的缓存帮助程序代码中,就像我一样

var item = builder().Result;

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

实际上可以缓存任务本身而不是结果,请参阅此处 https://devblogs.microsoft.com/dotnet/understanding-whys-whats-and-when's-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

最新更新