Механизм блокировки С# - блокировка только для записи

В продолжение моих последних размышлений о блокировках в C# и .NET,

Рассмотрим следующий сценарий:

У меня есть класс, который содержит определенную коллекцию (в этом примере я использовал Dictionary<string, int>), которая обновляется из источника данных каждые несколько минут с использованием определенного метода, тело которого вы можете увидеть ниже:

    DataTable dataTable = dbClient.ExecuteDataSet(i_Query).GetFirstTable();

    lock (r_MappingLock)
    {
        i_MapObj.Clear();

        foreach (DataRow currRow in dataTable.Rows)
        {
            i_MapObj.Add(Convert.ToString(currRow[i_Column1]), Convert.ToInt32[i_Column2]));
        }
    }

r_MappingLock — это объект, предназначенный для блокировки критической секции, которая обновляет содержимое словаря.

i_MapObj — объект словаря.

i_Column1 и i_Column2 — это имена столбцов таблицы данных, которые содержат нужные данные для сопоставления.

Теперь у меня также есть метод класса, который получает строку и возвращает правильное сопоставленное целое число на основе упомянутого словаря.

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

    lock (r_MappingLock)
    {
        int? retVal = null;

        if (i_MapObj.ContainsKey(i_Key))
        {
            retVal = i_MapObj[i_Key];
        }

        return retVal;
    }

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

Я думал о добавлении логического члена в класс, для которого будет установлено значение true или false, независимо от того, обновляется ли словарь или нет, и проверять его в методе «только для чтения», но это вызывает другие проблемы, связанные с состоянием гонки...

Любые идеи, как решить это изящно?

Спасибо еще раз,

Майки


person Mikey S.    schedule 14.08.2011    source источник


Ответы (5)


Взгляните на встроенный ReaderWriterLock.

person Albin Sunnanbo    schedule 14.08.2011
comment
@Mikey И обязательно прочитайте о его проблемах и ReaderWriterLockSlim - person Henk Holterman; 15.08.2011
comment
Не могли бы вы опубликовать несколько связанных ссылок? - person Mikey S.; 15.08.2011

Я бы просто переключился на использование ConcurrentDictionary, чтобы вообще избежать этой ситуации - вручную блокировка подвержена ошибкам. Также, как я могу узнать из "C#: The Curious ConcurrentDictionary", ConcurrentDictionary уже оптимизирован для чтения.

person BrokenGlass    schedule 14.08.2011
comment
Параллельные коллекции действительно хороши, если вы используете .NET 4. - person Albin Sunnanbo; 14.08.2011
comment
Я использую .NET 4, но на самом деле меня больше интересовала теоретическая идея решения, а также решение, которое подходило бы и для более старых версий .NET, тем не менее спасибо! - person Mikey S.; 14.08.2011

Албин правильно указал на ReaderWriterLock. Я добавлю еще более приятный: ReaderWriterGate Джеффри Рихтера. Наслаждаться!

person Vladimir    schedule 14.08.2011

Вы можете подумать о создании нового словаря при обновлении вместо блокировки. Таким образом, у вас всегда будут согласованные результаты, но чтение во время обновлений вернет предыдущие данные:

private volatile Dictionary<string, int> i_MapObj = new Dictionary<string, int>();

private void Update()
{
    DataTable dataTable = dbClient.ExecuteDataSet(i_Query).GetFirstTable();

    var newData = new Dictionary<string, int>();
    foreach (DataRow currRow in dataTable.Rows)
    {
        newData.Add(Convert.ToString(currRow[i_Column1]), Convert.ToInt32[i_Column2]));
    }

    // Start using new data - reference assignments are atomic
    i_MapObj = newData;
}

private int? GetValue(string key)
{
    int value;
    if (i_MapObj.TryGetValue(key, out value))
        return value;

    return null;
}
person Bojan Resnik    schedule 15.08.2011

В C# 4.0 есть класс ReaderWriterLockSlim, который намного быстрее! Почти так же быстро, как lock().

Сохраняйте политику запрета рекурсии (LockRecursionPolicy::NoRecursion), чтобы поддерживать высокую производительность.

Посмотреть на этой странице для получения дополнительной информации.

person Salvatore Previti    schedule 14.08.2011