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

ConcurrentDictionary<T,V> в .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();
}

Результат, показанный в окне Trace, показывает « Hello from t1 » и « Hello от t2 ». Это НЕ является желаемым поведением для большинства применений, которые мы используем, и подтверждает проблему, указанную в ссылке MSDN выше. Мы хотим, чтобы только эти один выполняли эти делегаты.

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

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

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, имеющее значение Lazy, приводит к тому, что делегат значения ТОЛЬКО выполняется один раз (вы либо видите « Hello from t1 ", либо " Привет от t2 ")!

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

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

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

28 голосов | спросил Adam Spicer 21 PMpThu, 21 Apr 2011 18:27:47 +040027Thursday 2011, 18:27:47

1 ответ


14
  • Предоставление вызывающему абоненту аргумента типа U означает, что им разрешено использовать подкласс Lazy<V>, но это не будет работать, поскольку ваши реализации всегда создает новый List<V> и передает его в U. Поскольку это означает, что U всегда должен быть Lazy<V>, то почему бы не покончить с дополнительным аргументом типа.

    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
    
ответил Brian Reichle 23 AMpSat, 23 Apr 2011 07:05:00 +040005Saturday 2011, 07:05:00

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132