首页 > 解决方案 > ConcurrentDictionary 线程安全替代方案中的 AddOrUpdate 方法

问题描述

根据此链接ConcurrentDictionary 中的 AddOrUpdate 线程是否安全?来自本页https://msdn.microsoft.com/en-us/library/dd287191(v=vs.110).aspx的“备注”部分。它说

“但是,这些方法的委托是在锁外调用的,以避免在锁下执行未知代码可能出现的问题。因此,这些委托执行的代码不受操作原子性的影响。”

问题是我在我的代码中广泛使用它并意识到我有一个潜在的非常讨厌的错误(我的理解一直是错误的)......因为我使用下面的方式我没想到可以在同时覆盖内部字典:

internal class HandlerProducers
{
    readonly ConcurrentDictionary<Type, Dictionary<Type, InstanceProducer>> handlerProducers
        = new ConcurrentDictionary<Type, Dictionary<Type, InstanceProducer>>();

    public void AddProducer(Type notificationType, Type concreteType, InstanceProducer producer)
    {
        this.handlerProducers
                .AddOrUpdate(
                    notificationType,
                    (key) => new Dictionary<Type, InstanceProducer> { { concreteType, producer } },
                    (key, dictionary) => { dictionary.Add(concreteType, producer); return dictionary; });
    }

    public IEnumerable<InstanceProducer> GetForHandler(Type notificationHandlerType)
    {
        if(this.handlerProducers.TryGetValue(notificationHandlerType, out var dict))
        {
            foreach (var kvp in dict)
                yield return kvp.Value;
        }
    }
}

我有一个进一步的挑战,天真地把锁放在适当的位置可能会导致热点,上面的类被广泛用于从 via 读取GetForHandler()并偶尔写入(通过AddProducer()method.

通过大量读取和偶尔写入的高性能来确保这是线程安全的最佳方法是什么?

标签: c#multithreadingconcurrentdictionary

解决方案


我建议使用嵌套ConcurrentDictionary结构:

readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, InstanceProducer>> handlerProducers
    = new ConcurrentDictionary<Type, ConcurrentDictionary<Type, InstanceProducer>>();

然后,您可以AddProducer像这样实现该方法:

public void AddProducer(Type notificationType, Type concreteType,
    InstanceProducer producer)
{
    ConcurrentDictionary<Type, InstanceProducer> innerDict =
        this.handlerProducers.GetOrAdd(notificationType,
            _ => new ConcurrentDictionary<Type, InstanceProducer>());

    if (!innerDict.TryAdd(concreteType, producer))
        throw new InvalidOperationException(
            $"{notificationType}.{concreteType} already exists.");
}

该方法GetForHandler无需修改。

此实现将使您的HandlerProducers类真正线程安全,而不会牺牲其效率。


推荐阅读