可以更新或删除的共享内存的线程安全枚举
本文关键字:内存 线程 安全 枚举 共享 更新 删除 | 更新日期: 2023-09-27 18:28:50
我在线程之间有一个共享对象,用于保存文件状态信息。保存信息的对象是此类:
/// <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=新的SynchronizedDictionary();
其他线程可以派生并使用该线程,如下所示:_cache["key"];
字典可以在运行时修改。我认为这里没有问题。还是我错了?在我看来,问题在于枚举器,因为我想制作一个遍历集合的枚举器。我该怎么做?我想到了这三种解决方案:
- 制作这样的枚举器:http://www.codeproject.com/Articles/56575/Thread-safe-enumeration-in-C(但使用ReaderWriterLockSlim)
- 像SyncRoot一样公开锁定对象(但使用ReaderWriterLockSlim),因此调用者调用enter和exit读取方法
- 请使用数据库(SQLite fx)来保存信息
数字1)的问题是:
- 它使用构造函数进入读取模式。如果GetEnumerator()是手动调用的,而不是使用foreach?忘记调用dispose
- 我不知道这是否是一个好的编码风格。尽管我喜欢代码
- 如果调用者使用foreach,我不知道调用者可能会做什么在枚举器的实例化和对dispose的调用之间。如果我理解了我正确阅读的文档,这可以只要还有一个读者在做,最终就会阻止作者一些繁重的工作
数字2)的问题是:
- 我不喜欢暴露这个。我知道.NET API能做到这一点,但是我不喜欢它
- 正确进出取决于呼叫者
我的眼睛没有问题。但我把这个小项目作为业余项目来做,我想了解更多关于多线程和反射的知识,所以我想把它作为最后的选择。我之所以想在运行时迭代集合,是因为我想找到符合某些条件的值。
也许只有我发明了一个问题?
我知道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个。
我会将您的问题视为反馈请求,以帮助您学习。让我谈谈你已经确定的三个解决方案:
- 是的,这就是为什么这样的设计永远不应该作为API公开给第三方(甚至其他开发人员)。正确使用很难。这篇代码项目文章有一些令人讨厌的建议
- 这要好得多,因为这个模型对锁定是明确的,而不是隐含的。然而,在我看来,这违反了关注点的分离
- 不知道你在这里是什么意思。你可以在你的字典上有一个Snapshot()方法,它做一个只读副本,可以安全地传递和阅读。这与解决方案1不同
有一个完全不同的解决方案:使用一个不可变的字典。即使在并发写访问的情况下,这样的字典也可以安全地传递、读取和枚举。这样的字典/地图通常使用树来实现。
我将详细阐述一个关键点:您需要将并发系统作为一个整体来考虑。你无法通过使所有组件线程安全(在你的情况下是一本字典)来使你的应用程序正确。您需要定义您使用字典来的内容。
你说:
我希望在运行时迭代集合的原因是我想找到符合某些标准的值。
您对数据进行了并发写入,并且希望从字典中原子地获得一致的快照(也许是为了在UI中拍摄一些进度报告?)。既然我们知道了这个目标,我们就可以设计一个解决方案:
您可以在字典中添加一个克隆方法,该方法在获取读取锁定的同时克隆所有数据。这将给调用者一个新的对象,然后它可以独立地对其进行枚举。这将是一个干净和安全的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();
}
}
}