1

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

/// <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 会怎样?忘记调用 dispose。
  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 个。

4

2 回答 2

2

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

  1. 是的,这就是为什么这样的设计不应该作为 API 暴露给第三方(甚至其他开发人员)。正确使用是很棘手的。这篇 codeproject 文章有一些讨厌的建议。
  2. 好多了,因为这个模型对锁定是明确的,而不是隐含的。然而,在我看来,这违反了关注点分离。
  3. 不知道你在这里的意思。您可以在字典上有一个 Snapshot() 方法,该方法执行只读副本,可以安全地传递和阅读。这是与解决方案 1 不同的权衡取舍。

完全有一个不同的解决方案:使用不可变字典。即使在并发写访问下,这样的字典也可以安全地传递、读取和枚举。这样的字典/地图通常使用树来实现。

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

你说:

我想在运行时迭代集合的原因是我想找到符合某些条件的值。

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

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

于 2012-03-24T22:40:50.527 回答
2

我不会直接实现,而是IEnumerable添加一个Values属性(如Dictionary.Values):

public IEnumerable<T> Values {
  get {
    _lock.EnterReadLock();
    try {
      foreach (T v in _collection.Values) {   
        yield return v;
      }
    } finally {
      _lock.ExitReadLock();
    }
  }
}
于 2012-03-24T22:44:57.227 回答