Потоковое перечисление разделяемой памяти, которое может быть обновлено или удалено
У меня есть общий объект между потоками, который используется для хранения информации о состоянии файла. Объект, который содержит информацию, является этим классом:
/// <summary>
/// A synchronized dictionary class.
/// Uses ReaderWriterLockSlim to handle locking. The dictionary does not allow recursion by enumeration. It is purly used for quick read access.
/// </summary>
/// <typeparam name="T">Type that is going to be kept.</typeparam>
public sealed class SynchronizedDictionary<U,T> : IEnumerable<T>
{
private System.Threading.ReaderWriterLockSlim _lock = new System.Threading.ReaderWriterLockSlim();
private Dictionary<U, T> _collection = null;
public SynchronizedDictionary()
{
_collection = new Dictionary<U, T>();
}
/// <summary>
/// if getting:
/// Enters read lock.
/// Tries to get the value.
///
/// if setting:
/// Enters write lock.
/// Tries to set value.
/// </summary>
/// <param name="key">The key to fetch the value with.</param>
/// <returns>Object of T</returns>
public T this[U key]
{
get
{
_lock.EnterReadLock();
try
{
return _collection[key];
}
finally
{
_lock.ExitReadLock();
}
}
set
{
Add(key, value);
}
}
/// <summary>
/// Enters write lock.
/// Removes key from collection
/// </summary>
/// <param name="key">Key to remove.</param>
public void Remove(U key)
{
_lock.EnterWriteLock();
try
{
_collection.Remove(key);
}
finally
{
_lock.ExitWriteLock();
}
}
/// <summary>
/// Enters write lock.
/// Adds value to the collection if key does not exists.
/// </summary>
/// <param name="key">Key to add.</param>
/// <param name="value">Value to add.</param>
private void Add(U key, T value)
{
_lock.EnterWriteLock();
if (!_collection.ContainsKey(key))
{
try
{
_collection[key] = value;
}
finally
{
_lock.ExitWriteLock();
}
}
}
/// <summary>
/// Collection does not support iteration.
/// </summary>
/// <returns>Throw NotSupportedException</returns>
public IEnumerator<T> GetEnumerator()
{
throw new NotSupportedException();
}
/// <summary>
/// Collection does not support iteration.
/// </summary>
/// <returns>Throw NotSupportedException</returns>
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
throw new NotSupportedException();
}
}
Я называю этот словарь так: SynchronizedDictionary _cache = new SynchronizedDictionary();
Другие потоки могут порождаться и использовать поток следующим образом: _cache["key"];
Словарь может быть изменен во время выполнения. Я не вижу здесь никаких проблем. Или я не прав? Проблема, на мой взгляд, заключается в перечислителе, потому что я хочу создать перечислитель, который перебирает всю коллекцию. Как мне это сделать? Я думал об этих трех решениях:
- Создание перечислителя следующим образом: http://www.codeproject.com/Articles/56575/Thread-safe-enumeration-in-C(но с использованием ReaderWriterLockSlim)
- Предоставьте объект блокировки, как это делает SyncRoot (но с ReaderWriterLockSlim), поэтому вызывающая сторона вызывает методы чтения входа и выхода.
- Вместо этого используйте базу данных (SQLite fx), содержащую информацию.
Проблема с номером 1):
- он использует конструктор для входа в режим чтения. Что если GetEnumerator() вызывается вручную, не используя foreach? И забудьте позвонить распоряжаться.
- Я не знаю, хороший ли это стиль кодирования. Хотя мне нравится код.
- Если вызывающая сторона использует foreach, я не знаю, что может делать вызывающая сторона между созданием экземпляра перечислителя и вызовом dispose. Если я понял документацию, которую я прочитал правильно, это может привести к блокировке писателя, пока один читатель выполняет тяжелую работу.
Проблема с номером 2):
- Я не люблю выставлять это. Я знаю, что.NET API это делает, но не нравится.
- Звонящий может самостоятельно входить и выходить
Там нет проблем с 3) Я мои глаза. Но я делаю этот небольшой проект как проект свободного времени, и я хочу больше узнать о многопоточности и рефлексии, поэтому я хочу оставить это как последний вариант. Причина, по которой я хочу перебирать коллекцию во время выполнения, состоит в том, что я хочу найти значения, которые соответствуют некоторым критериям.
Может быть, только я изобрел проблему?
Я знаю ConcurrentDictionary, но я не хочу использовать это. Я использую этот проект в качестве игровой площадки. Играя с потоками и отражением.
РЕДАКТИРОВАТЬ
Меня спросили, что я читаю и пишу. И я собираюсь рассказать об этом в этом редактировании. Я читаю и пишу этот класс:
public class AssemblyInformation
{
public string FilePath { get; private set; }
public string Name { get; private set; }
public AssemblyInformation(string filePath, string name)
{
FilePath = filePath;
Name = name;
}
}
Я делаю много чтений и почти не пишу во время выполнения. Возможно я сделаю 2000 и 1 напишу. Там также не будет много объектов, может быть, 200.
2 ответа
Я буду рассматривать ваши вопросы как запрос обратной связи, которая поможет вам учиться. Позвольте мне рассмотреть три решения, которые вы уже определили:
- Да, именно поэтому такой дизайн никогда не должен быть представлен как API сторонним разработчикам (или даже другим разработчикам). Это сложно использовать правильно. У этой статьи codeproject есть несколько неприятных советов.
- Намного лучше, потому что эта модель была бы явной о блокировке, а не неявной. Однако, по моему мнению, это нарушает разделение интересов.
- Не уверен, что вы имеете в виду здесь. В вашем словаре может быть метод Snapshot(), который делает копию только для чтения, которую можно безопасно передавать и читать. Это другой компромисс, чем решение 1.
Существует совершенно другое решение: использовать неизменный словарь. Такой словарь можно безопасно передавать, читать и перечислять даже при одновременном доступе к записи. Такие словари / карты обычно реализуются с использованием деревьев.
Я более подробно остановлюсь на ключевом моменте: вам нужно подумать о параллельной системе в целом. Вы не можете сделать ваше приложение корректным, сделав все компоненты поточно-ориентированными (в вашем случае это словарь). Вы должны определить, для чего вы используете словарь.
Ты говоришь:
Причина, по которой я хочу перебирать коллекцию во время выполнения, состоит в том, что я хочу найти значения, которые соответствуют некоторым критериям.
Если у вас есть параллельные записи, происходящие с данными, и вы хотите получить непротиворечивый моментальный снимок из словаря (может быть, выстрелили некоторый отчет о ходе выполнения в пользовательском интерфейсе?). Теперь, когда мы знаем эту цель, мы можем найти решение:
Вы можете добавить метод Clone в свой словарь, который клонирует все данные при взломе блокировки чтения. Это даст вызывающему объекту свежий объект, который он может затем перечислить самостоятельно. Это был бы чистый и надежный API.
Вместо реализации IEnumerable
непосредственно я бы добавил Values
собственность (как Dictionary.Values
):
public IEnumerable<T> Values {
get {
_lock.EnterReadLock();
try {
foreach (T v in _collection.Values) {
yield return v;
}
} finally {
_lock.ExitReadLock();
}
}
}