如何使用Lazy处理并发请求
本文关键字:并发 请求 处理 Lazy 何使用 | 更新日期: 2023-09-27 18:26:46
我是C#的新手,正在努力了解如何使用Lazy
。
我需要通过等待已经运行的操作的结果来处理并发请求。对数据的请求可能同时带有相同/不同的凭据。
对于每个唯一的凭据集,最多可以有一个GetDataInternal调用在进行中,当准备好时,该调用的结果将返回给所有排队的等待者
private readonly ConcurrentDictionary<Credential, Lazy<Data>> Cache
= new ConcurrentDictionary<Credential, Lazy<Data>>();
public Data GetData(Credential credential)
{
// This instance will be thrown away if a cached
// value with our "credential" key already exists.
Lazy<Data> newLazy = new Lazy<Data>(
() => GetDataInternal(credential),
LazyThreadSafetyMode.ExecutionAndPublication
);
Lazy<Data> lazy = Cache.GetOrAdd(credential, newLazy);
bool added = ReferenceEquals(newLazy, lazy); // If true, we won the race.
Data data;
try
{
// Wait for the GetDataInternal call to complete.
data = lazy.Value;
}
finally
{
// Only the thread which created the cache value
// is allowed to remove it, to prevent races.
if (added) {
Cache.TryRemove(credential, out lazy);
}
}
return data;
}
这是使用Lazy
的正确方式还是我的代码不安全?
更新:
开始使用MemoryCache
而不是ConcurrentDictionary
是个好主意吗?如果是,如何创建键值,因为它是MemoryCache.Default.AddOrGetExisting()
中的string
这是正确的。这是一个标准模式(删除除外),它是一个非常好的缓存,因为它可以防止缓存踩踏。
我不确定你是否想在计算完成后从缓存中删除,因为计算会一遍又一遍地重做。如果你不需要删除,你可以通过基本上删除后半部分来简化代码。
注意,Lazy
在发生异常的情况下有一个问题:异常被存储,工厂将永远不会重新执行。这个问题一直存在(直到用户重新启动应用程序)。在我看来,这使得Lazy
在大多数情况下完全不适合生产使用。
这意味着,网络问题等暂时性错误可能会使应用程序永久不可用。
此答案指向原始问题的更新部分。请参阅有关Lazy<T>
线程安全性和潜在陷阱的@usr答案。
我想知道如何避免使用
ConcurrentDictionary<TKey, TValue>
并启动使用MemoryCache?如何实施MemoryCache.Default.AddOrGetExisting()
?
如果您正在寻找一个具有自动过期机制的缓存,那么如果您不想自己实现该机制,那么MemoryCache
是一个不错的选择。
为了使用强制密钥使用字符串表示的MemoryCache
,您需要创建凭证的唯一字符串表示,可能是给定的用户id还是唯一的用户名?
如果可以的话,您可以创建ToString
的覆盖,它表示您的唯一标识符,或者简单地使用所述属性,并像这样使用MemoryCache
:
public class Credential
{
public Credential(int userId)
{
UserId = userId;
}
public int UserId { get; private set; }
}
现在你的方法看起来是这样的:
private const EvictionIntervalMinutes = 10;
public Data GetData(Credential credential)
{
Lazy<Data> newLazy = new Lazy<Data>(
() => GetDataInternal(credential), LazyThreadSafetyMode.ExecutionAndPublication);
CacheItemPolicy evictionPolicy = new CacheItemPolicy
{
AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(EvictionIntervalMinutes)
};
var result = MemoryCache.Default.AddOrGetExisting(
new CacheItem(credential.UserId.ToString(), newLazy), evictionPolicy);
return result != null ? ((Lazy<Data>)result.Value).Value : newLazy.Value;
}
MemoryCache
为您提供了一个线程安全的实现,这意味着访问AddOrGetExisting
的两个线程只会导致添加或检索单个缓存项。此外,Lazy<T>
和ExecutionAndPublication
仅保证对工厂方法的单个唯一调用。