C# 部分线程安全字典

C# 部分线程安全字典,c#,.net,thread-safety,C#,.net,Thread Safety,我有一个类,它维护一个私有的字典实例来缓存一些数据 该类使用ReaderWriterLockSlim从多个线程写入字典 我想在类外公开字典的值。 线程安全的方法是什么 现在,我有以下几点: public ReadOnlyCollection<MyClass> Values() { using (sync.ReadLock()) return new ReadOnlyCollection<MyClass>(cache.Values.ToArray()

我有一个类,它维护一个私有的
字典
实例来缓存一些数据

该类使用
ReaderWriterLockSlim
从多个线程写入字典

我想在类外公开字典的值。
线程安全的方法是什么

现在,我有以下几点:

public ReadOnlyCollection<MyClass> Values() {
    using (sync.ReadLock())
        return new ReadOnlyCollection<MyClass>(cache.Values.ToArray()); 
}
公共只读集合值(){
使用(sync.ReadLock())
返回新的ReadOnlyCollection(cache.Values.ToArray());
}
有没有一种方法可以做到这一点,而无需多次复制收藏


我使用的是.Net 3.5(不是4.0)

查看下一种可能性,只是公开了ICollection接口,所以在Values()中可以返回自己的实现。此实现将仅对dictionAnry.Values使用引用,并始终对access项使用ReadLock。

编辑:我个人认为以下代码在技术上正确地回答了您的问题(如中所述,它提供了一种在不创建副本的情况下枚举集合中的值的方法)。一些开发人员的声誉远远超过我强烈建议的反对这种方法,因为他们在编辑/评论中解释了原因。简而言之:这显然是个坏主意。因此我留下答案,但建议您不要使用它


除非我遗漏了什么,否则我相信您可以将您的值公开为
IEnumerable
,而无需使用
yield
关键字复制值:

public IEnumerable<MyClass> Values {
    get {
        using (sync.ReadLock()) {
            foreach (MyClass value in cache.Values)
                yield return value;
        }
    }
}
因此,如果要求此
属性等同于访问该属性时
缓存
中内容的快照,那么您必须制作一份副本

(begin 280Z28):下面是一个不熟悉“C#做事方式”的人如何锁定代码的示例:

IEnumerator enumerator = obj.Values.GetEnumerator();
MyClass first = null;
if (enumerator.MoveNext())
    first = enumerator.Current;
(end 280Z28)

如果您想要字典当前状态的快照,那么对于此集合类型,您真的没有其他方法可以做。这与属性使用的技术相同

如果在枚举集合时对其进行了修改,您不介意抛出一个
invalidoOperationException
,您可以返回
cache.Values
,因为它是只读的(因此不会损坏字典数据)

我想在类外公开字典的值。 线程安全的方法是什么

你有三个选择

1) 复制数据,分发副本。优点:不用担心线程安全地访问数据。缺点:客户端获取过期数据的副本,而不是最新数据。而且,复制是昂贵的

2) 分发一个对象,该对象在从中读取时锁定基础集合。您必须编写自己的只读集合,该集合引用“父”集合的锁。仔细设计这两个对象,以避免死锁。优点:从客户的角度来看:“公正有效”;他们可以获得最新的数据,而不必担心锁定问题。缺点:你还有更多的工作要做


3) 把问题推给客户。公开锁,并要求客户端在使用它之前锁定数据本身的所有视图。优点:你没有工作。缺点:为客户做更多的工作,他们可能不愿意或不能做。死锁等风险现在成为客户的问题,而不是您的问题。

这不起作用,因为锁是私有的。(该类的使用者无法进入锁)您的
产量中断
无效。此外,这可以是属性而不是方法。(因为它不会做昂贵的复制品)@Dan:我强烈建议你不要这样做。潜在的性能改进带来了字典在只读模式下死锁的巨大风险。说到线程安全代码,线程安全总是胜过性能。@Dan:我添加了一个“写得很差,但在其他方面是无辜的代码”的例子,这会锁定字典。这是一个非常非常糟糕的想法。从yield到下一次迭代之间可以经过任意多的时间,在这段时间内,其他代码可能触发事件,导致其他线程尝试访问集合。这个代码给用户一个定时炸弹,当它爆炸时会导致死锁。当你无法控制(1)在锁中花费了多少时间,以及(2)什么代码可以在锁中运行时,一定要避免取出锁。@280Z28:这是一个非常有用的观察结果,老实说,我肯定没有考虑过。就我个人而言,虽然我大体上同意“线程安全…胜过性能”,但我也认为在死锁是可能的,但可以避免的情况下,需要对这两种情况进行区分,如果开发团队得到了正确的信息——并且在性能关键场景中,特定类/方法/等的设计不可避免地会出现死锁,我可能仍然会选择上述方法,如果它提供了性能改进(我甚至不知道;我还没有对其进行分析)。
IEnumerator enumerator = obj.Values.GetEnumerator();
MyClass first = null;
if (enumerator.MoveNext())
    first = enumerator.Current;