Методы расширения, чтобы сделать ConcurrentDictionary GetOrAdd и AddOrUpdate потокобезопасным при использовании делегатов valueFactory


В ConcurrentDictionary в .Net 4.0-это потокобезопасным, но не все методы являются атомарными.

Это указывает на то, что:

... не все методы атомной, специально GetOrAdd и AddOrUpdate. Делегат пользователя, который передается этих методов вызывается за пределами внутреннего словаря замка.

Пример Проблемы:

Возможно, для метода делегата должны быть выполнены более чем один раз для данного ключа.

public static readonly ConcurrentDictionary<int, string> store =
    new ConcurrentDictionary<int, string>();

[TestMethod]
public void UnsafeConcurrentDictionaryTest()
{

    Thread t1 = new Thread(() =>
    {
        store.GetOrAdd(0, i =>
        {
            string msg = "Hello from t1";
            Trace.WriteLine(msg);
            Thread.SpinWait(10000);
            return msg;
        });
    });

    Thread t2 = new Thread(() =>
    {
        store.GetOrAdd(0, i =>
        {
            string msg = "Hello from t2";
            Trace.WriteLine(msg);
            Thread.SpinWait(10000);
            return msg;
        });
    });

    t1.Start();
    t2.Start();
    t1.Join();
    t2.Join();
}

Результат отображается в окне трассировки показывает "привет от T1" и "привет из Т2". Это не желаемое поведение для большинства реализаций, которые мы используем и подтверждает проблему, указанную в приведенной выше ссылке на MSDN. Что мы хотим, это только одна из тех делегатов, чтобы быть выполненным.

Предлагаемое Решение:

Я должен использовать делегат перегрузки этих методов, которые привели меня для дальнейшего изучения этого вопроса. Я наткнулся на этот пост, который предлагает использовать ленивый класса для обеспечения делегат вызывается только один раз. Имея это в виду, я написал следующее расширение методы маска добавлении ленивый фантик значение.

public static V GetOrAdd<T, U, V>(this ConcurrentDictionary<T, U> dictionary, T key, Func<T, V> valueFactory)
where U : Lazy<V>
{
    U lazy = dictionary.GetOrAdd(key, (U)new Lazy<V>(() => valueFactory(key)));
    return lazy.Value;
}

public static V AddOrUpdate<T, U, V>(this ConcurrentDictionary<T, U> dictionary, T key, Func<T, V> addValueFactory, Func<T, V, V> updateValueFactory)
where U : Lazy<V>
{
    U lazy = dictionary.AddOrUpdate(key, 
                (U)new Lazy<V>(() => addValueFactory(key)), 
                (k, oldValue) => (U)new Lazy<V>(() => updateValueFactory(k, oldValue.Value)));
    return lazy.Value;
}

Разрешение Испытания:

Выполнение того же теста, с помощью ConcurrentDictionary , что ленивый значения значение делегата выполняется только один раз (либо "привет от T1" или "Привет из Т2")!

public static readonly ConcurrentDictionary<int, Lazy<string>> safeStore =
            new ConcurrentDictionary<int, Lazy<string>>();

Так и кажется, что этот подход достиг цели.

Что вы думаете о таком подходе?



15269
30
задан 21 апреля 2011 в 02:04 Источник Поделиться
Комментарии
1 ответ


  • Что позволяет пользователю указать тип аргумента у означает, что они разрешили использовать подкласс ленивыйно это не будет работать, так как ваши разработки всегда создает новый список и бросить его у. Поскольку это означает, что у всегда должен быть ленивым тогда почему не покончить с дополнительным аргументом типа.

    public static V GetOrAdd<T, V>(this ConcurrentDictionary<T, Lazy<V>> dictionary, T key, Func<T, V> valueFactory)

  • Название Новые методы расширения конфликта с названиями существующих методов. Для потребителей, чтобы использовать ваш вместо существующими методами, они должны либо получить доступ через ваш статический класс или использовать явные аргументы типа. Это может привести к тонкие ошибки, когда потребители пытаются использовать его как метод расширения с выводом типа.

    ExtensionHost.GetOrAdd(safeStore, 7, (i) => i.ToString());             // uses yours
    safeStore.GetOrAdd<int, Lazy<string>, string>(6, (i) => i.ToString()); // uses yours
    safeStore.GetOrAdd(5, (i) => i.ToString()); // uses existing

14
ответ дан 23 апреля 2011 в 03:04 Источник Поделиться