如何使用Lazy处理并发请求?

我是C#的新手,并试图了解如何使用Lazy

我需要通过等待已经运行的操作的结果来处理并发请求。 对数据的请求可以与相同/不同的凭证同时进行。

对于每个唯一的凭证集合,最多可以有一个GetDataInternal调用正在进行中,其中一个调用的结果在准备就绪时返回给所有排队的服务员

 private readonly ConcurrentDictionary<Credential, Lazy> Cache = new ConcurrentDictionary<Credential, Lazy>(); public Data GetData(Credential credential) { // This instance will be thrown away if a cached // value with our "credential" key already exists. Lazy newLazy = new Lazy( () => GetDataInternal(credential), LazyThreadSafetyMode.ExecutionAndPublication ); Lazy 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在exception情况下存在问题:存储exception并且永远不会重新执行工厂。 问题永远存在(直到人重新启动应用程序)。 在我看来,这使得Lazy在大多数情况下完全不适合生产使用。

这意味着诸如网络问题之类的瞬态错误可能会导致应用永久不可用。

这个答案针对原始问题的更新部分。 有关Lazy线程安全性以及潜在的缺陷,请参阅@usr答案 。


我想知道如何避免使用ConcurrentDictionary并开始使用MemoryCache? 如何实现MemoryCache.Default.AddOrGetExisting()

如果您正在寻找具有自动过期机制的缓存,那么如果您不想自己实现这些机制,则MemoryCache是一个不错的选择。

为了利用强制键的字符串表示的MemoryCache ,您需要创建一个凭证的唯一字符串表示forms,可能是给定的用户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 newLazy = new Lazy( () => 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)result.Value).Value : newLazy.Value; } 

MemoryCache为您提供了线程安全的实现,这意味着访问AddOrGetExisting两个线程只会导致添加或检索单个缓存项。 此外,带有ExecutionAndPublication Lazy仅保证工厂方法的单个唯一调用。