无法安全地锁定ConcurrentDictionary的值

时间:2010-10-26 15:51:28

标签: c# .net multithreading concurrency c#-4.0

我无法锁定Collection中的项目 - 特别是ConcurrentDictionary。

我需要接受一条消息,在字典中查找该消息,然后对其进行长时间扫描。由于程序占用大量内存,因此在扫描后,如果对象认为是删除它的好时机(我将其从字典中删除),则返回 true 。但是,另一个线程可能会在相似的时间到来并尝试在删除后立即访问该对象。这是我的第一次尝试:

string dictionaryKey = myMessage.someValue;

DictionaryObject currentObject = myConcurrentDictionary.GetOrAdd(dictionaryKey, new DictionaryObject());
// we can be interrupted here
lock (currentObject)
{
    //KeyNotFoundException is possible on line below
    if (myConcurrentDictionary[dictonaryKey].scan(myMessage)) // Scans the message - returns true if the object says its OK to remove it from the dictionary
    {
      DictionaryObject temp;                      //   It's OK to delete it
      if (!queuedMessages.TryRemove(ric, out temp))   // Did delete work?
       throw new Exception("Was unable to delete a DictionaryObject that just reported it was ok to delete it");
    }
}

但是,上面的方法不起作用 - 在另一个线程试图访问Dictionary中的该对象之前,一个线程可能会从Dictionary中删除一个对象。阅读lock is shorthand for Monitor.Enter and Monitor.Exit之后,我尝试了这个:

string dictionaryKey = myMessage.someValue;
Monitor.Enter(GetDictionaryLocker);
DictionaryObject currentObject = myConcurrentDictionary.GetOrAdd(dictionaryKey, new DictionaryObject());
// we can be interrupted here
lock (currentObject)
{
    Monitor.Exit(GetDictionaryLocker);
    //KeyNotFoundException is still possible on line below
    if (myConcurrentDictionary[dictonaryKey].scan(myMessage)) // Scans the message - returns true if the object says its OK to remove it from the dictionary
    {
      DictionaryObject temp;                   //   It's OK to delete it
      if (!queuedMessages.TryRemove(ric, out temp))   // Did delete work?
       throw new Exception("Was unable to delete a DictionaryObject that just reported it was ok to delete it");
    }
}

当尝试在Dictionary中查找对象时,这两种方式都可能导致 KeyNotFoundException

有谁知道我怎么能找到我要锁定的对象,然后锁定它而不被中断?对不起 - 我是并发的新手,感到非常困惑!

谢谢,

弗雷德里克

4 个答案:

答案 0 :(得分:3)

在开始扫描之前,应该从字典中删除对象,以防止任何其他线程同时尝试使用它。如果您必须在scan()失败后再将其重新添加,则可以随时将其重新添加。删除和添加都保证在此并发集合上是线程安全的。

如果没有lockMonitor使用,这应该可以实现您的目标。

string dictionaryKey = myMessage.someValue;

DictionaryObject currentObject = null;
if (myConcurrentDictionary.TryRemove(dictionaryKey, out currentObject))
{
    //KeyNotFoundException is possible on line below
    if (!currentObject.scan(myMessage)) // Scans the message - returns true if the object says its OK to remove it from the dictionary
    {
      if (!myConcurrentDictionary.TryAdd(dictionaryKey, currentObject))
       throw new Exception("Was unable to re-insert a DictionaryObject that is not OK for deletion");
    }
} 

在不了解您的其他代码的情况下,我对此问题的关注是,在您致电scan()期间,某个其他线程是否可以使用相同的密钥添加回另一条消息。这将导致TryAdd失败。如果这是可能的话,需要做更多的工作。

您当前模型的问题在于,即使集合是线程安全的,如果您希望将“正在扫描”的项目保留在集合中,您真正必须做的是执行以下操作组合原子地:1。找到一个免费项目,然后2.将其标记为“正在使用”。

  1. 可以通过集合是线程安全的来完成,但
  2. 必须单独完成,因此您可以在同一个对象上打开多个scan()的窗口。

答案 1 :(得分:0)

我认为这里存在一个“锁定”对象的根本误解。

当您对某个对象(Monitor.Enter)进行锁定时,它实际上并不会阻止其他线程使用该对象。它只是阻止其他线程对该特定对象进行锁定。你需要做的是添加一个辅助对象,并锁定它 - 并确保每个线程都锁定它。

话虽如此,这将引入同步开销。可能值得尝试提出一种设计,其中仅在存在冲突时才使用锁定 - 例如在扫描之前实际移除对象,然后锁定以及在扫描期间保持锁定。如果他们请求的对象不是字典的一部分,其他线程只能尝试获取锁...

答案 2 :(得分:0)

这是推测......

问题是concurrentDictionary为每个线程返回Unique对象,以防止它们相互干扰。

任何添加,更改或删除方法都将确保访问正确的项目,但锁定只会锁定您的本地包装器。

任何解决方案都取决于你可以用字典做什么。

线程是否可以直接从字典中提取消息并且只在扫描后替换它,或者您是否可以使用遍历字典的主线程并将所有消息添加到线程从中拉出的队列对象以及处理后删除文档应该删除。

这样每个线程都不会争用同一条消息。

答案 3 :(得分:0)

如何在对象中包含一个字段来指示哪个线程拥有它,并使用Threading.Interlocked.CompareExchange来尝试获取它?如果一个对象正在使用中,代码不应该锁定,而只是放弃操作。