ConcurrentQueue
有TryDequeue
方法。
Queue
只有Dequeue
方法。
在ConcurrentDictionary
中没有Add
方法,但我们有TryAdd
代替。
我的问题是:
这些并发集合方法之间有什么区别?为什么并发集合的方法是不同的?
ConcurrentQueue
有TryDequeue
方法。
Queue
只有Dequeue
方法。
在ConcurrentDictionary
中没有Add
方法,但我们有TryAdd
代替。
我的问题是:
这些并发集合方法之间有什么区别?为什么并发集合的方法是不同的?
使用Dictionary<TKey, TValue>
时,需要自己实现逻辑来确保不会输入重复的键。例如:
if(!myDictionary.ContainsKey(key)) myDictionary.Add(key, value);
当我们有多个线程运行且可能同时尝试修改字典时,我们使用并发集合。
如果两个线程同时执行上述代码,则它们都在同时检查并未添加该键值对,因此myDictionary.ContainsKey(key)
可能会同时返回 false。 然后它们都尝试添加键,但只有一个成功了。
如果不知道这是多线程的代码,则会让读者感到困惑。我在添加之前检查确保键还不存在,那么为什么会引发异常?
ConcurrentDictionary.TryAdd
解决了这个问题,它允许您“尝试”添加键值对。 如果添加成功,则返回true
;如果添加失败,则返回false
。但是它不会与另一个TryAdd
产生冲突并引发异常。
您可以通过将Dictionary
包装在类中并在其周围放置lock
语句来自己完成所有这些操作。但是,ConcurrentDictionary
已经替你做到了这一点,并且做得非常好。您不必看到它的详细工作原理-您只需使用它,知道已经考虑了多线程问题。
在使用多线程应用程序中的类时,请注意以下细节。如果您转到ConcurrentDictionary Class文档并向下滚动,您将看到以下内容:
线程安全性
ConcurrentDictionary的所有公共和受保护成员均可在多个线程中同时使用,并且不需要进行同步处理。但是,通过ConcurrentDictionary实现的接口之一访问的成员(包括扩展方法)不能保证是线程安全的,可能需要由调用者进行同步处理。
换句话说,多个线程可以安全地读取和修改集合。
在Dictionary Class下,您会看到以下内容:
线程安全Queue.Dequeue
失败通常表示应用程序内部逻辑存在问题,因此在这种情况下抛出异常是好的。ConcurrentQueue.TryDeque
的失败可能在正常流程中是可以预期的,因此避免抛出异常并返回一个Boolean
是一种合理的处理方式。
(在 .NET 框架中,通常采用返回布尔结果而不是抛出异常的
ConcurrentQueue<T>
在内部处理所有同步。如果两个线程同时调用TryDequeue
,则不会阻塞任何操作。当检测到两个线程之间存在冲突时,一个线程必须再次尝试检索下一个元素,并且同步由内部处理。
Try...
函数,例如TryParse
方法。)Try
语义是因为按照设计,没有可靠的方法来确定Dequeue
或Add
操作是否成功。Dequeue
方法之前检查是否有任何内容可以出列。同样,可以检查非并发的Dictionary
中是否存在键。但是在并发类中,您不能这样做,因为在您检查它是否存在之后,其他人可能会将您的项从队列中删除。换句话说,Try
操作允许您原子地检查前提条件并执行该操作。由于这些集合是同时设计使用的,因此您不能依赖于按顺序检查前提条件,您需要进行原子操作。
以字典为例,通常您可以编写如下代码:
if (!dictionary.ContainsKey(key))
{
dictionary.Add(key, value);
}
来自MSDN:
尝试移除并返回并发队列开头的对象。
返回
如果成功从ConcurrentQueue的开头移除并返回元素,则返回true;否则返回false。
因此,如果您可以删除TryDequeue
,只需删除并返回它,如果无法删除,则返回false,并在队列空闲时再次尝试。