Правильный способ блокировки словарного объекта

В моем коде у меня есть объект статического словаря

private static IDictionary< ConnKey, DbConnection > ConnectionList = new Dictionary< ConnKey, DbConnection >( );

который выбрасывает эту ошибку

System.IndexOutOfRangeException: Index was outside the bounds of the array.
  at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
  at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)

Я искал и обнаружил, что это происходит, потому что несколько потоков пытаются получить доступ к словарю, но у меня есть lock в словаре

lock( ConnectionList ) {
   ConnectionList.Add( key, res );
}

Затем я искал больше и обнаружил, что блокировка словаря не предотвращает все операции над ним, поэтому я должен использовать объект lock on SyncRoot для этого, чтобы добиться того, что хочу

lock( ((IDictionary)ConnectionList).SyncRoot) {

Но потом я искал, что использование SyncRoot не является хорошей практикой

При дальнейшем поиске я обнаружил, что для этого существует ConcurrentDictionary

  • Так может кто-нибудь, пожалуйста, предложите мне, который является лучшим способом блокировки словаря.
  • Если я использую ConcurrentDictionary, мне все равно нужно использовать lock на нем или он сам будет обрабатывать все.
  • Если мне нужно использовать lock на ConcurrentDictionary, я должен использовать lock на нем напрямую или снова, мне нужно заблокировать объект SyncRoot для него

Спасибо заранее!

Ответ 1

С Dictionary<,> вам нужно заблокировать чтение и запись. Таким образом, оба

lock( ConnectionList ) {
   ConnectionList.Add( key, res );
}

и

lock( ConnectionList ) {
   res = ConnectionList[ key ];
}

и

lock( ConnectionList ) {
   int cnt = ConnectionList.Count;
}

и

lock( ConnectionList ) {
   ConnectionList.Clear();
}

и

lock( ConnectionList ) {
   foreach ( var kv in ConnectionList ) {
      // Do things
   }
}

и т.д.: -)

С ConcurrentDictionary<,> вам не нужна блокировка, но обратите внимание, что синтаксис немного отличается от синтаксиса Dictionary<,>

Ответ 2

  • Так может кто-нибудь, пожалуйста, предложите мне, который является лучшим способом блокировки словаря.

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

private static object _sybcRoot = new object();

public static void Add( string key, string res)
    lock( _sybcRoot ) {
       ConnectionList.Add( key, res );
    }
}
  1. Если я использую ConcurrentDictionary, мне все равно нужно использовать блокировку на нем или он сам будет обрабатывать все.

Нет, нет необходимости блокировать при использовании какой-либо коллекции Concurrent*. Это поточно-безопасный по дизайну, но этот синтаксис несколько отличается. Concurrent* коллекции используют запретный подход, что лучше в ситуациях, когда у вас нет большого количества потоков, конкурирующих за доступ (оптимистичный concurrency)

  1. Если мне нужно использовать lock в ConcurrentDictionary, я должен использовать блокировку непосредственно или снова, мне нужно заблокировать объект SyncRoot для него.

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

Ответ 3

Так может ли кто-нибудь предложить мне, который является лучшим способом блокировки словаря?

если вы хотите продолжить использование классического Dictionary<,> AFAK, вам нужно искать интерфейс ICollection, реализованный в словаре, и использовать свойство ICollection.SyncRoot который по определению

MSDN Gets an object that can be used to synchronize access to the ICollection. Чтобы достичь этого, вы можете сделать что-то вроде этого

Если я использую ConcurrentDictionary, мне все равно нужно использовать блокировку на нем или он будет обрабатывать все сам по себе.

От MSDN
ConcurrentDictionary предназначен для многопоточных сценариев. Вам не нужно использовать блокировки в вашем коде для добавления или удаления элементов из коллекции. Тем не менее, всегда возможно, чтобы один поток извлекал значение, а другой поток немедленно обновлял коллекцию, предоставляя тому же ключу новое значение.

Если мне нужно использовать lock на ConcurrentDictionary, я должен использовать блокировку на нем напрямую или снова, мне нужно заблокировать объект SyncRoot для него

Да, вы должны использовать lock на SyncRoot, если хотите выполнить выполнение Atomic-методов при использовании методов GetOrAdd или AddOrUpdate