.NET Dictionary.Resize() 异常 - 线程安全吗?

4
我们遇到了一个非常严重的问题,导致每分钟出现数千个异常。我们有一个网站,运行着一个自制的缓存机制,以以下形式保存数据:
protected static IDictionary<int, IList<IInterfaceForData>> m_Data = null;

当我们在这个字典上调用Add方法时,会出现非常奇怪的行为:“索引超出了数组边界”,即使该键明显不存在于字典中:

m_Data.Add(id, new List<IInterfaceForData>());

我们使用以下方式来保护此调用:

我们使用锁来保护此调用:

if(Monitor.TryEnter(m_LockObj, 1000))
{
   try
   {
       m_Data.Add(id, new List<IInterfaceForData>());
   }
   catch(Exception ex)
   {                                   
        // log exception
   }
   finally
   {
      Monitor.Exit(m_LockObj);
   }
}

然后我们会收到这个异常:

   at System.Collections.Generic.Dictionary`2.Resize()     at   System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)       at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)     

我们无法为此提供任何解释,因为异常与字典的线程安全相关,而我们(认为我们)是线程安全的。我们在每个Add()和Remove()调用上使用lock()或Monitor.TryEnter,除了m_Data.TryGetValue(...)。
非常感谢您的帮助。

2
дҪ иҝҳйңҖиҰҒй”Ғе®ҡTryGetValueд»ҘеҸҠд»»дҪ•е…¶д»–иҜ»еҸ–жҲ–жӣҙж–°еӯ—е…ёзҡ„ж“ҚдҪңгҖӮеҪ“е…¶д»–зәҝзЁӢеҗҢж—¶ж·»еҠ жҲ–еҲ йҷӨйЎ№ж—¶пјҢTryGetValueеҫҖеҫҖдјҡиЎЁзҺ°еҮәдёҚеҸҜйў„жөӢзҡ„иЎҢдёәгҖӮ - Jim Mischel
也许你正在混淆通用字典上的TryGetValue并发字典上的TryGetValue,因为它们都是最近引入的(.NET 3.5和.NET 4)。 - Justin
@Justin:什么?Dictionary<TKey, TValue>.TryGetValue早就存在了! - Dan Tao
@Jim,在TryGetValue上加锁会将代码更改为单读取器,而我希望它成为多读取器。 - Nir
@Nir: 使用ReaderWriterLockSlim。它允许N个读者或1个写者。 - Jim Mischel
显示剩余3条评论
2个回答

8
似乎在某些时候代码没有锁定,而且集合被更改了...你是否看过 System.Collections.Concurrent 命名空间?特别是 ConcurrentDictionary 类?它是线程安全的,可能会避免像那样奇怪的错误和竞态条件等问题。
它几乎像常规字典一样工作,除了大多数操作都使用“尝试”方法,例如 TryGetValue,它将尝试获取值并在操作有效时返回True,如果无效则返回False,然后您可以根据这些值继续执行逻辑。
您应该查看这个 msdn 链接,它与您正在做的非常相似: 使用 ConcurrentDictionary 实现缓存 提问者目前正在使用一个带有 ReaderWriterLockSlim 的非并发字典,并将其更改为并发字典。

谢谢,但我们使用的是 .net 3.5。ConcurrentDictionary 是在 .net 4 中的。 - Nir

4

您需要在任何地方同步访问您的m_Data字典,而不仅仅是在Add调用中。您是否这样做了?


是的,除了调用m_Data.TryGetValue(...)、Remove()和Add()方法之外,其他地方都使用lock()或Monitor.TryEnter()。谢谢! - Nir
1
@Nir:正如Jim Mischel在评论中指出的那样,仅在写入时加锁是不够的。您还必须在读取时加锁;否则,在读取操作进行中可能会修改集合。 - Dan Tao
在TryGetValue上加锁会将代码更改为单读取器,但我希望它是多读取器。 - Nir
2
@Nir:那么你正在使用错误的类。当Dictionary<TKey, TValue>正在被写入时,它只能支持多个并发读取器*。如果您没有将TryGetValue调用与写入同步,则所有赌注都将失效。如果您在.NET 3.5上,请下载Reactive Extensions库,并使用其中的后移ConcurrentDictionary<TKey, TValue>类。 - Dan Tao

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接