本文介绍了为什么 ConcurrentDictionary.GetOrAdd(key, valueFactory) 允许 valueFactory 被调用两次?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用并发字典作为线程安全的静态缓存并注意到以下行为:

I am using a concurrent dictionary as a thread-safe static cache and noticed the following behavior:

来自 MSDN 上的文档获取或添加:

如果您在不同的线程上同时调用 GetOrAdd,addValueFactory 可能会被多次调用,但它的键/值对可能不会每次调用都添加到字典中.

我希望能够保证工厂只被调用一次.有没有什么方法可以使用 ConcurrentDictionary API 做到这一点,而无需诉诸我自己的单独同步(例如,在 valueFactory 中锁定)?

我的用例是 valueFactory 在动态模块内生成类型,所以如果同一键的两个 valueFactories 同时运行,我点击:

My use case is that valueFactory is generating types inside a dynamic module so if two valueFactories for the same key run concurrently I hit:

System.ArgumentException:程序集中的类型名称重复.

推荐答案

你可以使用这样的字典:ConcurrentDictionary>,然后你的值工厂将返回一个 Lazy 对象,该对象已用 LazyThreadSafetyMode.ExecutionAndPublication 初始化,这是 Lazy 使用的默认选项> 如果你不指定它.通过指定 LazyThreadSafetyMode.ExecutionAndPublication,您告诉 Lazy 只有一个线程可以初始化并设置对象的值.

You could use a dictionary that is typed like this: ConcurrentDictionary<TKey, Lazy<TValue>>, and then the your value factory would return a Lazy<TValue> object that has been initialized with LazyThreadSafetyMode.ExecutionAndPublication, which is the default option used by Lazy<TValue> if you don't specify it. By specifying the LazyThreadSafetyMode.ExecutionAndPublication you are telling Lazy only one thread may initialize and set the value of the object.

这导致 ConcurrentDictionary 只使用 Lazy 对象的一个​​实例,而 Lazy 对象保护了更多超过一个线程初始化其值.

This results in the ConcurrentDictionary only using one instance of the Lazy<TValue> object, and the Lazy<TValue> object protects more than one thread from initializing its value.

var dict = new ConcurrentDictionary<int, Lazy<Foo>>();
dict.GetOrAdd(key,  
    (k) => new Lazy<Foo>(valueFactory)
);

缺点是每次访问字典中的对象时都需要调用 *.Value .这里有一些 扩展 可以帮助解决这个问题.

The downside then is you'll need to call *.Value every time you are accessing an object in the dictionary. Here are some extensions that'll help with that.

public static class ConcurrentDictionaryExtensions
{
    public static TValue GetOrAdd<TKey, TValue>(
        this ConcurrentDictionary<TKey, Lazy<TValue>> @this,
        TKey key, Func<TKey, TValue> valueFactory
    )
    {
        return @this.GetOrAdd(key,
            (k) => new Lazy<TValue>(() => valueFactory(k))
        ).Value;
    }

    public static TValue AddOrUpdate<TKey, TValue>(
        this ConcurrentDictionary<TKey, Lazy<TValue>> @this,
        TKey key, Func<TKey, TValue> addValueFactory,
        Func<TKey, TValue, TValue> updateValueFactory
    )
    {
        return @this.AddOrUpdate(key,
            (k) => new Lazy<TValue>(() => addValueFactory(k)),
            (k, currentValue) => new Lazy<TValue>(
                () => updateValueFactory(k, currentValue.Value)
            )
        ).Value;
    }

    public static bool TryGetValue<TKey, TValue>(
        this ConcurrentDictionary<TKey, Lazy<TValue>> @this,
        TKey key, out TValue value
    )
    {
        value = default(TValue);

        var result = @this.TryGetValue(key, out Lazy<TValue> v);

        if (result) value = v.Value;

        return result;
   }

   // this overload may not make sense to use when you want to avoid
   //  the construction of the value when it isn't needed
   public static bool TryAdd<TKey, TValue>(
       this ConcurrentDictionary<TKey, Lazy<TValue>> @this,
       TKey key, TValue value
   )
   {
       return @this.TryAdd(key, new Lazy<TValue>(() => value));
   }

   public static bool TryAdd<TKey, TValue>(
       this ConcurrentDictionary<TKey, Lazy<TValue>> @this,
       TKey key, Func<TKey, TValue> valueFactory
   )
   {
       return @this.TryAdd(key,
           new Lazy<TValue>(() => valueFactory(key))
       );
   }

   public static bool TryRemove<TKey, TValue>(
       this ConcurrentDictionary<TKey, Lazy<TValue>> @this,
       TKey key, out TValue value
   )
   {
       value = default(TValue);

       if (@this.TryRemove(key, out Lazy<TValue> v))
       {
           value = v.Value;
           return true;
       }
       return false;
   }

   public static bool TryUpdate<TKey, TValue>(
       this ConcurrentDictionary<TKey, Lazy<TValue>> @this,
       TKey key, Func<TKey, TValue, TValue> updateValueFactory
   )
   {
       if ([email protected](key, out Lazy<TValue> existingValue))
           return false;

       return @this.TryUpdate(key,
           new Lazy<TValue>(
               () => updateValueFactory(key, existingValue.Value)
           ),
           existingValue
       );
   }
}

这篇关于为什么 ConcurrentDictionary.GetOrAdd(key, valueFactory) 允许 valueFactory 被调用两次?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-16 22:50