简体   繁体   English

部分线程安全的字典

[英]Partially thread-safe dictionary

I have a class that maintains a private Dictionary instance that caches some data. 我有一个类维护一个缓存一些数据的私有Dictionary实例。

The class writes to the dictionary from multiple threads using a ReaderWriterLockSlim . 该类使用ReaderWriterLockSlim从多个线程写入字典。

I want to expose the dictionary's values outside the class. 我想在课外公开字典的值。
What is a thread-safe way of doing that? 什么是线程安全的方法呢?

Right now, I have the following: 现在,我有以下内容:

public ReadOnlyCollection<MyClass> Values() {
    using (sync.ReadLock())
        return new ReadOnlyCollection<MyClass>(cache.Values.ToArray()); 
}

Is there a way to do this without copying the collection many times? 有没有办法这样做而不需要多次复制集合?

I'm using .Net 3.5 (not 4.0) 我正在使用.Net 3.5(不是4.0)

I want to expose the dictionary's values outside the class. 我想在课外公开字典的值。 What is a thread-safe way of doing that? 什么是线程安全的方法呢?

You have three choices. 你有三个选择。

1) Make a copy of the data, hand out the copy. 1)复制数据,分发副本。 Pros: no worries about thread safe access to the data. 优点:无需担心线程安全访问数据。 Cons: Client gets a copy of out-of-date data, not fresh up-to-date data. 缺点:客户获得过时数据的副本,而不是新鲜的最新数据。 Also, copying is expensive. 而且,复制是昂贵的。

2) Hand out an object that locks the underlying collection when it is read from. 2)在读取时,分发一个锁定底层集合的对象。 You'll have to write your own read-only collection that has a reference to the lock of the "parent" collection. 您必须编写自己的只读集合,该集合引用了“父”集合的锁定。 Design both objects carefully so that deadlocks are impossible. 仔细设计两个对象,以便无法实现死锁。 Pros: "just works" from the client's perspective; 优点:从客户的角度来看“正常”; they get up-to-date data without having to worry about locking. 他们获得最新数据,而不必担心锁定。 Cons: More work for you. 缺点:为您做更多的工作。

3) Punt the problem to the client. 3)将问题提交给客户。 Expose the lock, and make it a requirement that clients lock all views on the data themselves before using it. 公开锁定,并要求客户端在使用之前锁定数据本身的所有视图。 Pros: No work for you. 优点:没有为你工作。 Cons: Way more work for the client, work they might not be willing or able to do. 缺点:为客户提供更多的工作,他们可能不愿意或无法做的工作。 Risk of deadlocks, etc, now become the client's problem, not your problem. 死锁等风险现在成为客户的问题,而不是您的问题。

If you want a snapshot of the current state of the dictionary, there's really nothing else you can do with this collection type. 如果您想要获取字典当前状态的快照 ,那么您可以使用此集合类型进行其他任何操作。 This is the same technique used by the ConcurrentDictionary<TKey, TValue>.Values property. 这与ConcurrentDictionary<TKey, TValue>.Values属性使用的技术相同。

If you don't mind throwing an InvalidOperationException if the collection is modified while you are enumerating it, you could just return cache.Values since it's readonly (and thus can't corrupt the dictionary data). 如果您在枚举时修改了集合,如果不介意抛出InvalidOperationException则可以返回cache.Values因为它只是readonly(因此不能破坏字典数据)。

EDIT : I personally believe the below code is technically answering your question correctly (as in, it provides a way to enumerate over the values in a collection without creating a copy). 编辑 :我个人认为下面的代码在技术上正确地回答了你的问题(因为,它提供了一种枚举集合中的值而无需创建副本的方法)。 Some developers far more reputable than I strongly advise against this approach, for reasons they have explained in their edits/comments. 由于他们在编辑/评论中已经解释过的原因,一些开发人员比我强烈建议反对这种方法更有信誉。 In short: This is apparently a bad idea. 简而言之: 这显然是一个坏主意。 Therefore I'm leaving the answer but suggesting you not use it. 因此,我将离开答案,但建议你不要使用它。


Unless I'm missing something, I believe you could expose your values as an IEnumerable<MyClass> without needing to copy values by using the yield keyword: 除非我遗漏了某些内容,否则我相信您可以将您的值公开为IEnumerable<MyClass>而无需使用yield关键字复制值:

public IEnumerable<MyClass> Values {
    get {
        using (sync.ReadLock()) {
            foreach (MyClass value in cache.Values)
                yield return value;
        }
    }
}

Be aware, however (and I'm guessing you already knew this), that this approach provides lazy evaluation , which means that the Values property as implemented above can not be treated as providing a snapshot . 但是请注意(我猜你已经知道这一点),这种方法提供了惰性求值 ,这意味着上面实现的Values属性不能被视为提供快照

In other words... well, take a look at this code (I am of course guessing as to some of the details of this class of yours): 换句话说......好吧,看看这段代码(我当然会猜测你这类的一些细节):

var d = new ThreadSafeDictionary<string, string>();

// d is empty right now
IEnumerable<string> values = d.Values;

d.Add("someKey", "someValue");

// if values were a snapshot, this would output nothing...
// but in FACT, since it is lazily evaluated, it will now have
// what is CURRENTLY in d.Values ("someValue")
foreach (string s in values) {
    Console.WriteLine(s);
}

So if it's a requirement that this Values property be equivalent to a snapshot of what is in cache at the time the property is accessed, then you're going to have to make a copy. 因此,如果要求此Values属性等同于访问该属性时cache中的内容的快照 ,那么您将不得不进行复制。

(begin 280Z28): The following is an example of how someone unfamiliar with the "C# way of doing things" could lock the code: (开始280Z28):以下是一个不熟悉“C#做事方式”的人如何锁定代码的例子:

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

(end 280Z28) (结束280Z28)

Review next possibility, just exposes ICollection interface, so in Values() you can return your own implementation. 回顾下一个可能性,只是公开ICollection接口,所以在Values()中你可以返回自己的实现。 This implementation will use only reference on Dictioanry.Values and always use ReadLock for access items. 此实现仅使用Dictioanry.Values上的引用,并始终使用ReadLock访问项。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM