为什么ConcurrentDictionary.GetOrAdd(key,valueFactory)允许valueFactory被调用两次? [英] Why does ConcurrentDictionary.GetOrAdd(key, valueFactory) allow the valueFactory to be invoked twice?

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

问题描述

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

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

GetOrAdd的MSDN文档


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

If you call GetOrAdd simultaneously on different threads, addValueFactory may be called multiple times, but its key/value pair might not be added to the dictionary for every call.

我想能够保证工厂只被调用一次。有没有办法这样做与ConcurrentDictionary API,而不求助于我自己的单独的同步(例如锁定在内部valueFactory)?

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

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:一个程序集。

System.ArgumentException: Duplicate type name within an assembly.


推荐答案

您可以使用类型如下的字典: ConcurrentDictionary< TKey,Lazy< TValue>> ,然后您的值工厂将返回一个 Lazy< TValue> 已使用 LazyThreadSafetyMode.ExecutionAndPublication 初始化。通过指定 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. By specifying the LazyThreadSafetyMode.ExecutionAndPublication you are telling Lazy only one thread may initialize and set the value of the object.

这导致 ConcurrentDictionary 只使用 Lazy< TValue> 对象的一个​​实例, c> Lazy< TValue> 对象保护多个线程初始化其值。

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.

ie

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

这样做的缺点是每次访问对象时都需要调用* .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),
                LazyThreadSafetyMode.ExecutionAndPublication
        )).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),
                LazyThreadSafetyMode.ExecutionAndPublication
            ),
            (k, currentValue) => new Lazy<TValue>(
                () => updateValueFactory(k, currentValue.Value),
                LazyThreadSafetyMode.ExecutionAndPublication
            )
        ).Value;
    }

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

        Lazy<TValue> v;
        var result = @this.TryGetValue(key, out 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),
               LazyThreadSafetyMode.ExecutionAndPublication
           )
       );
   }

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

       Lazy<TValue> v;
       if (@this.TryRemove(key, out 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
   )
   {
       Lazy<TValue> existingValue;
       if(!@this.TryGetValue(key, out existingValue))
           return false;

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

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

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆