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

the MSDN docs on GetOrAdd:



我希望能够保证工厂仅被调用一次。有什么方法可以使用ConcurrentDictionary API做到这一点,而无需依靠我自己的单独同步功能(例如,将其锁定在valueFactory内)?

我的用例是valueFactory在动态模块内生成类型,因此如果同一键的两个valueFactories同时运行,我会命中:
System.ArgumentException: Duplicate type name within an assembly.

最佳答案

您可以使用类型如下的字典:ConcurrentDictionary<TKey, Lazy<TValue>>,然后您的值工厂将返回一个已使用Lazy<TValue>初始化的LazyThreadSafetyMode.ExecutionAndPublication对象,如果未指定,则该默认选项是Lazy<TValue>使用的默认选项。通过指定LazyThreadSafetyMode.ExecutionAndPublication,您告诉Lazy只有一个线程可以初始化和设置对象的值。

这将导致ConcurrentDictionary仅使用Lazy<TValue>对象的一个​​实例,并且Lazy<TValue>对象可防止多个线程初始化其值。

IE。

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

不利的一面是,每次访问字典中的对象时,您都需要调用* .Value。这里有一些extensions可以帮助您。
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
       );
   }
}

关于c# - 为什么ConcurrentDictionary.GetOrAdd(key,valueFactory)允许valueFactory被调用两次?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/12611167/

10-12 16:30