可以更新或删除的共享内存的线程安全枚举

时间:2012-03-24 18:51:51

标签: c# multithreading dictionary thread-safety readerwriterlockslim

我在线程之间有一个共享对象,用于保存文件状态信息。保存信息的对象是这个类:

/// <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),因此调用者调用enter和exit读取方法。
  3. 使用数据库(SQLite fx)代替,保存信息。
  4. 数字1的问题是:

    1. 它使用构造函数进入读取模式。怎么样的呢? GetEnumerator()是手动调用,而不是使用foreach?忘了 呼吁处理。
    2. 我不知道这是不是一个好的编码风格。即使我喜欢 代码。
    3. 如果呼叫者使用foreach,我不知道呼叫者可能会做什么 在枚举器的实例化和处理的调用之间。 如果我已经理解了我已经阅读的文档,这可以 只要有一个读者离开,最终会阻止作者 一些繁重的工作。
    4. 数字2的问题是:

      1. 我不喜欢暴露这个。我知道.NET API可以做到,但是 不喜欢它。
      2. 由来电者正确进入和退出
      3. 没有问题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 个答案:

答案 0 :(得分:2)

我会将您的问题视为反馈请求,以帮助您学习。让我谈谈你已经确定的三个解决方案:

  1. 是的,这就是为什么这样的设计永远不应该作为API暴露给第三方(甚至其他开发人员)的原因。使用正确很棘手。这个代码项目文章有一些讨厌的建议。
  2. 好多了,因为这个模型对于锁定是明确的,而不是隐含的。然而,这在我看来违反了关注点。
  3. 不确定你的意思。您可以在字典上使用Snapshot()方法执行只读副本,该副本可以安全地传递和读取。这是与解决方案1不同的权衡。
  4. 完全有另一种解决方案:使用不可变字典。即使在并发写访问下,这样的字典也可以安全地传递,读取和枚举。这些词典/地图通常使用树来实现。

    我将详细阐述一个关键点:您需要考虑整个并发系统。通过使所有组件都是线程安全的(在您的情况下是字典),您无法使应用程序正确。您需要定义,使用字典 for

    你说:

      

    我想在运行时迭代集合的原因是   我希望找到符合某些标准的值。

    您是否发生了对数据的并发写入,并希望从字典中原子地获取一致的快照(可能在UI中拍摄一些进度报告?)。现在我们知道了这个目标,我们可以设计一个解决方案:

    您可以在字典中添加克隆方法,该方法在读取锁定时克隆所有数据。这将为调用者提供一个新的对象,然后可以单独枚举。这将是一个干净且安全的可曝光API。

答案 1 :(得分:1)

我没有直接实施IEnumerable,而是添加Values属性(例如Dictionary.Values):

public IEnumerable<T> Values {
  get {
    _lock.EnterReadLock();
    try {
      foreach (T v in _collection.Values) {   
        yield return v;
      }
    } finally {
      _lock.ExitReadLock();
    }
  }
}