为什么 ConcurrentQueue 和 ConcurrentDictionary 有“Try”方法 - TryAdd、TryDequeue - 而不是 Add 和 Dequeue?

Kam*_*mil 4 c# collections concurrentdictionary concurrent-queue

ConcurrentQueueTryDequeue方法。

Queue就有Dequeue方法了。

没有ConcurrentDictionary方法Add,但我们有TryAdd

我的问题是:

这些并发收集方法有什么区别?为什么它们对于并发集合不同?

Sco*_*nen 5

假设Dictionary<TKey, TValue>您将实现自己的逻辑以确保不会输入重复的键。例如,

if(!myDictionary.ContainsKey(key)) myDictionary.Add(key, value);
Run Code Online (Sandbox Code Playgroud)

但是,当我们有多个线程运行时,我们会使用并发集合,并且它们可能会同时尝试修改字典。

如果两个线程尝试同时执行上述代码,则myDictionary.ContainsKey(key)两个线程可能都返回 false,因为它们都在同时检查并且该密钥尚未添加。然后他们都尝试添加密钥,但其中一个失败了。

阅读该代码的人如果不知道它是多线程的,可能会感到困惑。在添加密钥之前,我检查过以确保该密钥不在字典中。那么我如何获得例外呢?

ConcurrentDictionary.TryAdd通过允许您“尝试”添加密钥来解决这个问题。如果它添加了它返回的值true。如果没有,则返回false. 但它不会与另一个发生冲突TryAdd并抛出异常。

Dictionary您可以通过将 包装在一个类中并在其周围放置语句来自己完成所有这些操作lock,以确保一次只有一个线程进行更改。ConcurrentDictionary只是为您做这件事,而且做得非常好。您不必查看它如何工作的所有细节 - 您只需在知道多线程已被考虑在内的情况下使用它即可。

以下是在多线程应用程序中使用类时需要注意的细节。如果您转到ConcurrentDictionary Class的文档并滚动到底部,您将看到以下内容:

线程安全ConcurrentDictionary 的
所有公共和受保护成员都是线程安全的,并且可以 在多个线程中同时使用。但是,通过 ConcurrentDictionary 实现的接口之一访问的成员(包括扩展方法)不保证线程安全,并且可能需要由调用者同步。

换句话说,多个线程可以安全地读取和修改集合。

在字典类下你会看到:

线程安全
字典可以支持多个 读者并发,只要集合不被修改。即便如此,通过集合进行枚举本质上并不是线程安全的过程。在枚举与写访问发生冲突的极少数情况下,必须在整个 枚举期间锁定集合 。要允许多个线程访问集合以进行读写,您必须实现自己的同步。

多个线程可以读取键,但如果多个线程要写入那么您需要以某种方式检查lock字典以确保一次只有一个线程尝试更新。

Dictionary<TKey, TValue>公开一个Keys集合和Values集合,以便您可以枚举键和值,但如果另一个线程要修改字典,它会警告您不要尝试这样做。在添加或删除项目时无法枚举某些内容。如果您需要迭代键或值,则必须锁定字典以防止迭代期间更新。

ConcurrentDictionary<TKey, TValue>假设将有多个线程读取和写入,因此它甚至不会公开键或值集合供您枚举。