Pergunta

Eu tenho um loop Parallel.ForEach executando uma operação intensiva no interior do corpo.

A operação pode usar um Hashtable para armazenar os valores, e pode ser reutilizado para outros itens de loop consecutivos. I acrescentar ao Hashtable após a operação intensiva estiver concluída, o próximo item loop pode olhar para cima na tabela de hash e reutilizar o objeto, em vez de executar a operação intensiva novamente.

No entanto, porque eu estou usando Parallel.ForEach há um problema inseguro, causando o Hashtable.Add eo (key) ContainsKey chamadas ir fora de sincronia, como eles podem ser executados em paralelo. Apresentando bloqueios podem causar perf questões.

Aqui está o código de exemplo:

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
}

Deve haver alguma API, definindo a propriedade dentro da biblioteca TPL, que poderia lidar com esse cenário. Existe?

Foi útil?

Solução

Você está procurando System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue> . As novas colecções simultâneas utilizam significativamente melhorada mecanismos de bloqueio e deve executar excellectly em algoritmos paralelos.

Edit: O resultado pode ter esta aparência:

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
} );

Palavra de advertência: se os elementos em items não têm item.Key único, então SomeIntensiveOperation poderia ter chamado duas vezes para essa chave. No exemplo, a chave não é passado para SomeIntensiveOperation, mas isso significa que o código "fazer algo com valor" poderia executar chave / valorA e pares chave / valorB, e apenas um resultado seria ficam armazenados no cache (não necessariamente o um primeiro calculado por SomeIntensiveOperation ambos). Você precisaria de uma fábrica preguiçoso paralelo para lidar com este se é um problema. Além disso, por razões óbvias SomeIntensiveOperation deve ser thread-safe.

Outras dicas

Use um ReaderWriterLock, isso tem um bom desempenho para o trabalho que tem muitas leituras e algumas gravações que são de curta duração. Seu problema parece se encaixar esta especificação.

Todas as lidas operações será executado rapidamente e bloquear livre, a única vez que alguém será bloqueado é quando uma gravação está acontecendo, e que escrever é apenas o tempo que for preciso para empurrar algo em um Hashtable.

ReaderWriterLockSlim no MSDN

Eu acho que vou derrubar algum código ...

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;
}

Não vejo outra escolha correta do que fechaduras uso (mais ou menos explícita) (A sincronizado Hashtable apenas substitui todos os métodos com fechaduras).

Outra opção poderia ser a de permitir que o dicionário para ir fora de sincronia. A condição de corrida não vai corromper o dicionário, ela só vai exigir que o código para fazer alguns cálculos supérfluos. Perfil o código para verificar se o bloqueio ou memoization faltando tem efeitos piores.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top