C# Can';t不能安全地锁定ConcurrentDictionary的值

C# Can';t不能安全地锁定ConcurrentDictionary的值,c#,.net,multithreading,concurrency,c#-4.0,C#,.net,Multithreading,Concurrency,C# 4.0,我在锁定集合中的某个项时遇到问题—特别是ConcurrentDictionary 我需要接受一条消息,在字典中查找该消息,然后对其进行长时间扫描。由于程序占用了大量内存,扫描后,如果对象认为是删除它的好时机(我通过从字典中删除它),则返回true。但是,另一个线程可能会在类似的时间出现,并在删除之后尝试访问同一对象。这是我第一次尝试: string dictionaryKey = myMessage.someValue; DictionaryObject currentObject = myC

我在锁定集合中的某个项时遇到问题—特别是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");
    }
}
但是,上述方法不起作用-一个线程可能会在另一个线程尝试访问字典中的对象之前从字典中删除该对象。读完后,我试着这样做:

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");
    }
}
当试图在字典中查找对象时,这两种方法都会导致出现KeyNotFoundException

有人知道我如何找到想要锁定的对象,然后锁定它而不被打断吗?抱歉-我对并发性不太熟悉,感到非常困惑

谢谢


弗雷德里克

我认为这里对“锁定”对象有一个基本的误解

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


这就是说,这将引入同步开销。可能值得尝试一种设计,在这种设计中,只有当存在冲突时才使用锁——比如在扫描之前实际移除对象,然后锁定,并在扫描期间保持锁。其他线程只能在其请求的对象不是字典的一部分时尝试获取锁…

在开始扫描之前,您应该从字典中删除该对象,以防止任何其他线程尝试同时使用它。如果以后在
scan()
中出现故障,您可以随时将其添加回。在这个并发集合上,remove和add都保证线程安全

这将使您无需使用任何
s或
监视器
即可实现所需功能

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。将其标记为“正在使用”

  • 由于集合是线程安全的,但是
  • 必须单独完成,因此您可以在同一对象上打开一个窗口,显示多个
    scan()
    s

  • 这是猜测

    问题是concurrentDictionary向每个线程返回唯一的对象,以防止它们相互干扰

    任何add、change或remove方法都将确保访问正确的项,但锁只会锁定本地包装器

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

    线程是否可以直接从字典中提取消息并仅在扫描后替换它,或者您是否可以使用一个主线程来遍历字典并将所有消息添加到线程从中提取并在处理完删除文档后删除的队列对象


    这样,每个线程将永远不会争用同一条消息。

    在对象中包含一个字段,指示线程拥有它,然后使用Threading.Interlocked.CompareExchange尝试获取它怎么样?如果一个对象正在使用,代码不应该锁定,而应该简单地放弃操作。

    我不认为您必须向ConcurrentDictionary添加自定义锁定?它是线程安全的吗?嗨,Dismissile,是的,它是线程安全的,但是查找我想要的密钥然后实际获取我的锁的操作并不是线程安全的。@dismisilie-你是对的,这里的问题是无法自动检索对象并将其标记为“正在处理”,以避免并发
    scan()
    s.也许你会选择
    ConcurrentQueue
    ConcurrentDicrional
    适用于映射。所以,将下一个工作(处理)项出列并处理它。若您需要在流程之前进行一些映射,那个么映射工作项(但它已经超出了处理队列,所以并没有并发问题),谢谢您的建议。您、Reed和supercat提到的“先删除项”策略听起来很优雅,但我不确定如何处理另一个线程在字典中查找该项,但没有找到它-然后它会希望创建对象的新副本-另一个线程已“签出”,应该等待另一个线程。这有什么意义吗?@Frederik,为什么其他线程也会有类似的
    消息
    。如果必须对消息进行处理,那么它必须如何与以前的消息堆叠?如何解决冲突,即当消息处理开始但另一个类似消息到达时?处理如何修改
    字典对象的状态
    可能是另一个选项,然后,使用队列将消息随机发送到工作线程。这样,每个线程都将获得单独的消息,没有冲突,字典将仍然包含所有消息,直到它们被标识为要删除。感谢您的回复。你说得对-首先删除一个项目听起来非常优雅。但是,我不知道该如何告诉其他线程该对象正在处理中,而不是