Механизм блокировки C # - запись только блокировки - PullRequest
4 голосов
/ 14 августа 2011

В продолжение моих последних размышлений о блокировках в 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 - это имена столбцов таблицы данных, которые содержат требуемые данные для сопоставления.

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

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

    lock (r_MappingLock)
    {
        int? retVal = null;

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

        return retVal;
    }

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

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

Есть идеи, как решить это изящно?

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

Mikey

Ответы [ 5 ]

8 голосов
/ 14 августа 2011

Посмотрите на встроенный ReaderWriterLock.

4 голосов
/ 14 августа 2011

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

2 голосов
/ 14 августа 2011

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

1 голос
/ 15 августа 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;
}
0 голосов
/ 15 августа 2011

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

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

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

...