Потоковое перечисление разделяемой памяти, которое может быть обновлено или удалено - PullRequest
1 голос
/ 24 марта 2012

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

/// <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 ["ключ"];

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

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

Проблема с номером 1 состоит в том, что:

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

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

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

Нет проблем с 3) Iмои глаза.Но я делаю этот небольшой проект как проект свободного времени, и я хочу узнать больше о многопоточности и рефлексии, поэтому я хочу оставить это в качестве последнего варианта.Причина, по которой я хочу перебирать коллекцию во время выполнения, состоит в том, что я хочу найти значения, которые соответствуют некоторым критериям.

Может быть, только я изобрел проблему?

Я знаю 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 ]

2 голосов
/ 25 марта 2012

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

  1. Да, именно поэтому такой дизайн никогда не должен представляться в виде API стороннему (или даже другим разработчикам).Это сложно использовать правильно.Эта статья о проекте кода имеет несколько неприятных советов.
  2. Намного лучше, потому что эта модель будет явной о блокировке, а не неявной.Однако, по моему мнению, это нарушает разделение интересов.
  3. Не уверен, что вы имеете в виду здесь.В вашем словаре может быть метод Snapshot (), который делает копию только для чтения, которую можно безопасно передавать и читать.Это другой компромисс, чем решение 1.

Существует совершенно другое решение: использовать неизменный словарь.Такой словарь можно безопасно передавать, читать и перечислять даже при одновременном доступе к записи.Такие словари / карты обычно реализуются с использованием деревьев.

Я более подробно остановлюсь на ключевом моменте: вам нужно подумать о параллельной системе в целом.Вы не можете сделать ваше приложение корректным, сделав все компоненты поточно-ориентированными (в вашем случае это словарь).Вам нужно определить, что вы используете в словаре для .

Вы говорите:

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

У вас есть параллельные записи, происходящие с данными, и вы хотите получить непротиворечивый моментальный снимок из словаря (возможно, для того, чтобы снять некоторый отчет о ходе выполнения впользовательский интерфейс?).Теперь, когда мы знаем эту цель, мы можем разработать решение:

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

1 голос
/ 25 марта 2012

Вместо непосредственной реализации IEnumerable я бы добавил свойство Values (например, Dictionary.Values):

public IEnumerable<T> Values {
  get {
    _lock.EnterReadLock();
    try {
      foreach (T v in _collection.Values) {   
        yield return v;
      }
    } finally {
      _lock.ExitReadLock();
    }
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...