Потоковое перечисление разделяемой памяти, которое может быть обновлено или удалено

У меня есть общий объект между потоками, который используется для хранения информации о состоянии файла. Объект, который содержит информацию, является этим классом:

/// <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"];

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

  1. Создание перечислителя следующим образом: http://www.codeproject.com/Articles/56575/Thread-safe-enumeration-in-C(но с использованием ReaderWriterLockSlim)
  2. Предоставьте объект блокировки, как это делает SyncRoot (но с ReaderWriterLockSlim), поэтому вызывающая сторона вызывает методы чтения входа и выхода.
  3. Вместо этого используйте базу данных (SQLite fx), содержащую информацию.

Проблема с номером 1):

  1. он использует конструктор для входа в режим чтения. Что если GetEnumerator() вызывается вручную, не используя foreach? И забудьте позвонить распоряжаться.
  2. Я не знаю, хороший ли это стиль кодирования. Хотя мне нравится код.
  3. Если вызывающая сторона использует foreach, я не знаю, что может делать вызывающая сторона между созданием экземпляра перечислителя и вызовом dispose. Если я понял документацию, которую я прочитал правильно, это может привести к блокировке писателя, пока один читатель выполняет тяжелую работу.

Проблема с номером 2):

  1. Я не люблю выставлять это. Я знаю, что.NET API это делает, но не нравится.
  2. Звонящий может самостоятельно входить и выходить

Там нет проблем с 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 ответа

Решение

Я буду рассматривать ваши вопросы как запрос обратной связи, которая поможет вам учиться. Позвольте мне рассмотреть три решения, которые вы уже определили:

  1. Да, именно поэтому такой дизайн никогда не должен быть представлен как API сторонним разработчикам (или даже другим разработчикам). Это сложно использовать правильно. У этой статьи codeproject есть несколько неприятных советов.
  2. Намного лучше, потому что эта модель была бы явной о блокировке, а не неявной. Однако, по моему мнению, это нарушает разделение интересов.
  3. Не уверен, что вы имеете в виду здесь. В вашем словаре может быть метод 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();
    }
  }
}
Другие вопросы по тегам