Я очень новичок в многопоточности, и по какой-то причине этот класс дает мне больше проблем, чем нужно.
Я создаю словарь в кеше ASP.net - он будет часто запрашиваться для отдельных объектов, периодически перечисляться и записываться крайне редко. Отмечу, что данные словаря почти никогда не меняются, я планирую, чтобы он истекал ежедневно с обратным вызовом для перестройки из базы данных, когда он покидает кеш.
Я считаю, что перечисление и доступ к ключам безопасны, пока словарь не записывается. Я думаю, что класс-оболочка на основе ReaderWriterLockSlim - это путь, но я нечеткий в нескольких точках.
Если я использую Lock, я считаю, что могу либо заблокировать токен, либо фактический объект, который я защищаю. Я не вижу, как сделать что-то подобное с помощью LockWriter Lock. Правильно ли я полагаю, что несколько экземпляров моей оболочки не будут правильно блокироваться, поскольку ReaderWriterLocks находятся вне каждой области?
Какова наилучшая практика для написания обертки? Построение его как статичного почти кажется излишним, поскольку основной объект поддерживается кешем. Синглтон, похоже, нахмурился, и я беспокоюсь о вышеупомянутых проблемах для отдельных случаев.
Я видел несколько реализаций подобных оболочек, но я не смог ответить на эти вопросы. Я просто хочу убедиться, что у меня есть твердое понимание того, что я делаю, вместо того, чтобы разрезать и прокладывать себе путь. Большое вам спасибо за помощь!
** Edit: Надеюсь, это более ясное резюме того, что я пытаюсь выяснить - **
1. Правильно ли я полагаю, что блокировка не влияет на базовые данные и ограничена как любая другая переменная?
В качестве примера можно сказать, что у меня есть следующее -
MyWrapperClass
{
ReaderWriterLockSlim lck = new ReaderWriterLockSlim();
Do stuff with this lock on the underlying cached dictionary object...
}
MyWrapperClass wrapA = new MyWrapperClass();
MyWrapperClass wrapB = new MyWrapperClass();
Правильно ли я думаю, что блокировка lockA и wrapB не будут взаимодействовать, и что если wrapA и wrapB будут пытаться выполнять операции, это будет небезопасно?
2. Если это так, то каков наилучший способ "поделиться" данными блокировки?
Это приложение Asp.net - будет несколько страниц, которым необходимо получить доступ к данным, поэтому я делаю это в первую очередь. Какова наилучшая практика для обеспечения того, чтобы различные обертки использовали один и тот же замок? Должна ли моя обертка быть статичной или синглетной, которую используют все потоки, если не самая элегантная альтернатива?
У вас есть несколько объектов словаря в кэше, и вы хотите, чтобы каждый из них блокировался независимо. "Лучший" способ - просто использовать простой класс, который сделает это за вас.
public class ReadWriteDictionary<K,V>
{
private readonly Dictionary<K,V> dict = new Dictionary<K,V>();
private readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
public V Get(K key)
{
return ReadLock(() => dict[key]);
}
public void Set(K key, V value)
{
WriteLock(() => dict.Add(key, value));
}
public IEnumerable<KeyValuePair<K, V>> GetPairs()
{
return ReadLock(() => dict.ToList());
}
private V2 ReadLock<V2>(Func<V2> func)
{
rwLock.EnterReadLock();
try
{
return func();
}
finally
{
rwLock.ExitReadLock();
}
}
private void WriteLock(Action action)
{
rwLock.EnterWriteLock();
try
{
action();
}
finally
{
rwLock.ExitWriteLock();
}
}
}
Cache["somekey"] = new ReadWriteDictionary<string,int>();
Также есть более подробный пример на странице справки ReaderWriterLockSlim в MSDN. Нетрудно сделать это родовым.
edit Чтобы ответить на ваши новые вопросы -
1.) Вы правильно wrapA, и wrapB не будет взаимодействовать. У них обоих есть свой экземпляр ReaderWriterLockSlim.
2.) Если вам нужна общая блокировка среди всех ваших классов-оболочек, тогда она должна быть статичной.
ConcurrentDictionary делает все, что угодно, а затем и некоторые. Часть System.Concurrent.Collections
Стандартный способ блокировки: object lck = new object(); ... lock(lck) { ... }
в этом случае объект lck представляет блокировку.
ReadWriterLockSlim не сильно отличается, его только в этом случае класс ReadWriterLockSlim представляет фактическую блокировку, поэтому везде, где вы бы использовали lck, теперь вы используете ReadWriterLockSlim.
ReadWriterLockSlim lck = new ReadWriterLockSlim();
...
lck.EnterReadLock();
try
{
...
}
finally
{
lck.ExitReadLock();
}