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

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

问题描述

我使用的是并发的字典作为一个线程安全的静态缓存,发现以下行为:



从的上GetOrAdd MSDN文档:




如果你调用GetOrAdd同时在不同的线程,
addValueFactory可以多次调用,但其键/值对
可能不会被添加到词典中的每个呼叫。




我想是能够保证工厂只调用一次。有没有什么办法与ConcurrentDictionary API来做到这一点,而不诉诸我自己单独的同步(如内部valueFactory锁定)?



我的用例是valueFactory正在生成类型的动态模块内部因此,如果两个valueFactories对于同一个密钥并发运行我打:




System.ArgumentException:内有重复的类型名。组装



解决方案

您可以使用键入像这样的词典: ConcurrentDictionary< TKEY的,懒惰的< TValue>> ,然后在你的价值工厂将返回懒< TValue>有对象被初始化为 LazyThreadSafetyMode.ExecutionAndPublication 。通过指定 LazyThreadSafetyMode.ExecutionAndPublication 你告诉懒只有一个线程可以初始化并设置对象的值。



这导致懒<的 ConcurrentDictionary 只使用一个实例; TValue> 对象和懒< TValue> 对象保护多个线程进行初始化其值




$ b。 $ b

  VAR字典=新ConcurrentDictionary< INT,懒<富>>(); 
dict.GetOrAdd(
键,
(K)=>新建懒<富>(
valueFactory,
LazyThreadSafetyMode.ExecutionAndPublication
));



然后,缺点是你需要调用* .value的每一次你在访问对象时词典。这里有一些扩展那将与帮助

 公共静态类ConcurrentDictionaryExtensions 
{
公共静态TValue GetOrAdd< TKEY的,TValue>(
本ConcurrentDictionary< TKEY的,懒惰的< TValue>> @this,
TKEY的关键,Func键< TKEY的,TValue> valueFactory

{
返回@ this.GetOrAdd(
键,
(K)=>新建懒< TValue>(
()=> valueFactory(K),
LazyThreadSafetyMode.ExecutionAndPublication
))。值;
}

公共静态TValue AddOrUpdate< TKEY的,TValue>(
本ConcurrentDictionary< TKEY的,懒惰的< TValue>> @this,
TKEY的关键,Func键< TKEY的,TValue> addValueFactory,
Func键< TKEY的,TValue,TValue> updateValueFactory

{
返回@ this.AddOrUpdate(
键,
( k)的=>新建懒&所述; TValue>(
()= GT; addValueFactory(k)时,
LazyThreadSafetyMode.ExecutionAndPublication
)中,
(K,CurrentValue的)= GT;新懒人< TValue>(
()=> updateValueFactory(K,currentValue.Value),
LazyThreadSafetyMode.ExecutionAndPublication

).value的;
}

公共静态布尔TryGetValue< TKEY的,TValue>(
本ConcurrentDictionary< TKEY的,懒惰的< TValue>> @this,
TKEY的关键,走出TValue价值

{
值=默认(TValue);

&懒LT; TValue>伏;
VAR的结果= @ this.TryGetValue(键,走出V);

如果(结果)值= v.Value;

返回结果;
}

//当你想避免
//价值的建筑时,不需要
公开其此重载可能没有意义使用静态布尔TryAdd< TKEY的,TValue>(
本ConcurrentDictionary< TKEY的,懒惰的< TValue>> @this,
TKEY的关键,TValue价值

{
返回@ this.TryAdd(关键,新的懒惰< TValue>(()=>值));
}

公共静态布尔TryAdd< TKEY的,TValue>(
本ConcurrentDictionary< TKEY的,懒惰的< TValue>> @this,
TKEY的关键,Func键< TKEY的,TValue> valueFactory

{
返回@ this.TryAdd(
键,
新懒人< TValue>(
()=> valueFactory(键),
LazyThreadSafetyMode.ExecutionAndPublication

);
}

公共静态布尔TryRemove< TKEY的,TValue>(
本ConcurrentDictionary< TKEY的,懒惰的< TValue>> @this,
TKEY的关键,走出TValue价值

{
值=默认(TValue);

&懒LT; TValue>伏;
如果(@ this.TryRemove(键,走出V))
{
值= v.Value;
返回真;
}
返回FALSE;
}

公共静态布尔TryUpdate< TKEY的,TValue>(
本ConcurrentDictionary< TKEY的,懒惰的< TValue>> @this,
TKEY的关键,Func键< TKEY的,TValue,TValue> updateValueFactory

{
&懒LT; TValue> existingValue;
如果
返回false(@ this.TryGetValue(键,出existingValue)!);

返回@ this.TryUpdate(
键,
新懒人< TValue>(
()=> updateValueFactory(键,existingValue.Value),
LazyThreadSafetyMode.ExecutionAndPublication

existingValue
);
}
}


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

From the MSDN docs on GetOrAdd:

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.

I would like to be able to guarantee that the factory is only called once. Is there any way to do this with the ConcurrentDictionary API without resorting to my own separate synchronization (e. g. locking inside 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: Duplicate type name within an assembly.

解决方案

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.

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.

i.e.

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

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(键,valueFactory)允许valueFactory被调用两次?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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