C# 在Parallel.ForEach中使用哈希表?

C# 在Parallel.ForEach中使用哈希表?,c#,.net,parallel-extensions,task-parallel-library,C#,.net,Parallel Extensions,Task Parallel Library,我有一个Parallel.ForEach循环在体内运行一个密集的操作 该操作可以使用哈希表存储值,并且可以对其他连续循环项重复使用。我在密集操作完成后添加到哈希表中,下一个循环项可以在哈希表中查找并重用对象,而不是再次运行密集操作 但是,由于我使用的是Parallel.ForEach,因此存在一个不安全的问题,导致Hashtable.Add和ContainsKey(key)调用不同步,因为它们可能并行运行。引入锁可能会导致性能问题 以下是示例代码: Hashtable myTable = new

我有一个Parallel.ForEach循环在体内运行一个密集的操作

该操作可以使用哈希表存储值,并且可以对其他连续循环项重复使用。我在密集操作完成后添加到哈希表中,下一个循环项可以在哈希表中查找并重用对象,而不是再次运行密集操作

但是,由于我使用的是Parallel.ForEach,因此存在一个不安全的问题,导致Hashtable.Add和ContainsKey(key)调用不同步,因为它们可能并行运行。引入锁可能会导致性能问题

以下是示例代码:

Hashtable myTable = new Hashtable;
Parallel.ForEach(items, (item, loopState) =>
{
    // If exists in myTable use it, else add to hashtable
    if(myTable.ContainsKey(item.Key))
    {
       myObj = myTable[item.Key];
    }
    else
    {
       myObj = SomeIntensiveOperation();
       myTable.Add(item.Key, myObj); // Issue is here : breaks with exc during runtime
    }
    // Do something with myObj
    // some code here
}

TPL库中必须有一些API和属性设置,可以处理这种情况。有吗?

除了使用(或多或少显式的)锁(同步哈希表只是用锁覆盖所有方法),我看不到其他正确的选择

另一个选择是允许字典不同步。竞争条件不会损坏字典,它只需要代码进行一些多余的计算。配置代码以检查锁定或丢失的备忘录是否会产生更坏的影响。

您正在寻找的。新的并发集合使用了显著改进的锁定机制,应该在并行算法中表现出色

编辑:结果可能如下所示:

ConcurrentDictionary<T,K> cache = ...;
Parallel.ForEach(items, (item, loopState) =>
{
    K value;
    if (!cache.TryGetValue(item.Key, out value))
    {
        value = SomeIntensiveOperation();
        cache.TryAdd(item.Key, value);
    }

    // Do something with value
} );
ConcurrentDictionary缓存=。。。;
Parallel.ForEach(items,(item,loopState)=>
{
K值;
如果(!cache.TryGetValue(item.Key,out值))
{
value=SomeIntensiveOperation();
cache.TryAdd(item.Key,value);
}
//做有价值的事
} );

警告词:如果
项中的元素不都具有唯一的
项。键
,则该键可能会调用两次
某些强度操作
。在本例中,键没有传递给
SomeIntensiveOperation
,但这意味着“使用值做点什么”代码可以执行键/值A和键/值B对,并且只有一个结果会存储在缓存中(不一定是由SomeIntensiveOperation计算的第一个结果)。如果这是一个问题,您需要一个并行的懒惰工厂来处理它。此外,出于显而易见的原因,某些IntensifiveOperation应该是线程安全的。

使用ReaderWriterLock,这对于具有多个读取和少量写入的短时间工作具有良好的性能。您的问题似乎符合此规范

所有读取操作都将快速运行且无锁,任何人被阻止的唯一时间是写操作发生时,而写操作的时间仅与将某些内容放入哈希表所需的时间相同

我想我会写一些代码

ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
Hashtable myTable = new Hashtable();
Parallel.ForEach(items, (item, loopState) =>
{
    cacheLock.EnterReadLock();
    MyObject myObj = myTable.TryGet(item.Key);
    cacheLock.ExitReadLock();

    // If the object isn't cached, calculate it and cache it
    if(myObj == null)
    {
       myObj = SomeIntensiveOperation();
       cacheLock.EnterWriteLock();
       try
       {
           myTable.Add(item.Key, myObj);
       }
       finally
       {
           cacheLock.ExitWriteLock();
       }           
    }
    // Do something with myObj
    // some code here
}

static object TryGet(this Hashtable table, object key)
{
    if(table.Contains(key))
        return table[key]
    else
        return null;
}

检查我认为您需要的名称空间

.NET Framework有两个读写器锁,ReaderWriterLockSlim和ReaderWriterLock。ReaderWriterLockSlim建议用于所有新开发。ReaderWriterLockSlim与ReaderWriterLock类似,但它简化了递归规则以及升级和降级锁状态的规则。ReaderWriterLockSlim避免了许多潜在死锁的情况。此外,ReaderWriterLockSlim的性能明显优于ReaderWriterLockSlim。“这个建议听起来不错,所以我更新了我的答案。对于那些感兴趣的人,请看一看这篇MSDN杂志文章:为什么这篇文章没有与
HashTable.Synchronized()相同的问题
导致双线程竞争条件,其中两个线程都从
TryGet
获取
null
返回值,然后都计算
myObj
并尝试添加它?这只意味着使对哈希表的访问成为原子的;它不会进行密钥冲突检测,但最坏的情况应该是效率较低的最后一次写入(>1个线程执行相同的工作。)也可能是
Syncrhonized()使用的锁
是静态的,是为了支持兼容,而
ReaderWriterLockSlim
显然已经更新,以通过不同的锁定模型避免许多旧的死锁情况。@AdamRalph:因为他正在使用TPL库,所以他已经在使用.net 4了。0@Adam&Yassir:没错,新系列的设计考虑到了并行LINQ。是的,谢谢你的支持答案和评论