valueFactory в параллельном словаре

Я пишу программу winform для тестирования параллельного словаря С# с классом ниже:

public class Class1
{
    public int X = 10;

    public Class1(int x)
    {
        X = x;
        Debug.WriteLine("Class1 Created");
    }
}

и ниже код кнопки:

  private void button1_Click(object sender, EventArgs e)
    {
        var dict = new ConcurrentDictionary<int, Class1>();

        Func<Class1> valueFactory = () => 
        {
            Debug.WriteLine("Factory Called");
            return new Class1(5);
        };

        var temp = dict.GetOrAdd(1, valueFactory());
        Debug.WriteLine(temp.X);
        temp.X = 20;

        var temp2 = dict.GetOrAdd(1, valueFactory());
        Debug.WriteLine(temp2.X);
    }

Я заметил, что метод valueFactory всегда выполнялся, а конструктор Class1 вызывался дважды, даже если ключ уже существует в dict после первого метода GetorAdd.

Однако, если я изменю определение Func на

Func<int, Class1> valueFactory = (k) => 
        {
            Debug.WriteLine("Factory Called");
            return new Class1(5);
        };

и вызовите метод GetorAdd, как показано ниже:

var temp = dict.GetOrAdd(1, valueFactory);

Программа работает так, как нужно, так как во втором вызове не вызывала конструктор Class1. Я подозреваю, что это потому, что я передал делегат valueFactory вместо вызова функции valueFactory() в метод GetorAdd. Интересно, есть ли подробное объяснение того, что происходит под капотом, и я также не понимаю, почему я не могу не передать valueFactory в качестве делегата, если мое определение Func отличается от Func<int, Class1 (то же определение, что и словарь)

Спасибо.


person Sean    schedule 01.09.2015    source источник


Ответы (1)


Когда вы делаете:

var temp = dict.GetOrAdd(1, valueFactory());
...
var temp2 = dict.GetOrAdd(1, valueFactory());

На самом деле вы вызываете valueFactory до вызова dict.GetOrAdd(). Так что это нормально, что он вызывается каждый раз. Конечно, новый экземпляр Class1, возвращенный вторым вызовом valueFactory(), в конечном итоге бесполезен, но тем не менее он был создан.

Напротив, когда вы делаете:

var temp = dict.GetOrAdd(1, valueFactory);
...
var temp2 = dict.GetOrAdd(1, valueFactory);

... на самом деле вы используете другую перегрузку метода GetOrAdd, где вы передаете ссылку делегату valueFactory, не вызывая его самостоятельно. Затем метод GetOrAdd() решает, нужно ли ему вызывать valueFactory, в зависимости от того, найден ли ключ в словаре.

Метод ConcurrentDictionary.GetOrAdd (TKey, Func) документ:

Добавляет пару ключ/значение в ConcurrentDictionary<TKey, TValue> с помощью указанной функции, если ключ еще не существует.

В этом случае ему не нужно вызывать его во второй раз, потому что он находит ключ во второй раз, когда вы вызываете GetOrAdd().


Однако обратите внимание, что передача valueFactory в качестве делегата GetOrAdd не гарантирует, что он не будет вызван дважды. Это особенно верно в многопоточных сценариях. Обратите внимание, что документация по методу ConcurrentDictionary.GetOrAdd (TKey, Func) также говорит об этом:

Если вы вызываете GetOrAdd одновременно в разных потоках, addValueFactory может вызываться несколько раз, но его пара ключ/значение может не добавляться в словарь при каждом вызове.

person sstan    schedule 01.09.2015