在Dictionary中使用对象锁定会产生KeyNotFoundException

the*_*man 6 c# multithreading dictionary locking task-parallel-library

我有一些代码可以response并行处理我的数据库中的许多对象(使用AsParallel()).每个response都有很多components.该responses可以共享相同的组件.我对component数据进行了一些修改并将其保存到db中,因此我需要防止多个线程同时处理同一个component对象.

我使用锁来实现这一目标.我有一个ConcurrentDictionary<int, object>可以容纳所有必要的锁定对象.像这样:

private static ConcurrentDictionary<int, object> compLocks = new ConcurrentDictionary<int, object>(); 
var compIds = db.components.Select(c => c.component_id).ToList();
foreach (var compId in compIds)
{
    compLocks[compId] = new object();
}
Run Code Online (Sandbox Code Playgroud)

然后我会这样做:

responses.AsParallel().ForAll(r =>
{

    ... do some time consuming stuff with web services ...

    // this is a *just in case* addition, 
    // in case a new component was added to 
    // the db since the dictionary was constructed
    // NOTE: it did not have any effect, and I'm no longer 
    // using it as @Henk pointed out it is not thread-safe.
    //if (compLocks[c.component_id] == null)
    //{
    //    compLocks[c.component_id] = new object();
    //}

    componentList.AsParallel().ForAll(c =>
    {
        lock (compLocks[c.component_id])
        {
            ... do some processing, save the db records ...
        }
    });
});
Run Code Online (Sandbox Code Playgroud)

这似乎运行得很好但是在程序执行结束时(由于有大量数据,它运行了几个小时)我得到以下异常:

未处理的异常:System.AggregateException:发生一个或多个错误.---> System.Collections.Generic.KeyNotFoundException:字典中没有给定的键.at System.Collections.Concurrent.ConcurrentDictionary`2.get_Item(TKey key)

我确信ConcurrentDictionary正在填充每个可能的componentID.

我有3个问题:

  1. 这个异常怎么可能,我该如何解决?
  2. 我需要ConcurrentDictionary这个吗?
  3. 在这种情况下,我对锁定如何正确的理解是否有更好的方法?

答案后编辑

为了弄清楚所有这一切的原因是什么,它.AsParallel()不会枚举集合responses.它是惰性评估的,意味着可以在运行时(来自其他进程)将新的responses(以及新的components)添加到集合中..ToList().AsParallel()修复问题之前执行快照.

我在运行时添加componentID的代码compLocks没有解决这个问题是因为它不是线程安全的.

Hen*_*man 3

1)这个例外怎么可能?

显然是这样,但不仅仅是从发布的代码来看。如果将数据添加到数据库中,就会发生这种情况(是否可以选择预先捕获responsesToList()

2)我需要一个 ConcurrentDictionary 吗?

不是使用固定列表,但是当解决方案涉及缺失时添加时,那么是的,您需要一个并发集合。

3)我对这种情况下锁定如何工作的理解是否正确/是否有更好的方法?

不完全确定。锁定看起来不错,但您仍然需要多次处理重复项。只是不同时而已。


对编辑的反应:

if (compLocks[c.component_id] == null)
{
    compLocks[c.component_id] = new object();
}
Run Code Online (Sandbox Code Playgroud)

这不是线程安全的。现在可以为 1 个 component_id 值创建多个锁定对象。您需要使用其中一种GetOrAdd()方法。

但我不希望这会给出您所得到的异常,所以这可能不是直接的问题。