Pergunta

Minha equipe está atualmente debatendo esse problema.

O código em questão é algo parecido com

if (!myDictionary.ContainsKey(key))
{
    lock (_SyncObject)
    {
        if (!myDictionary.ContainsKey(key))
        {
            myDictionary.Add(key,value);
        }
    }
}

Algumas das postagens que vi dizem que isso pode ser um grande NÃO NÃO (ao usar TryGetValue).Mesmo assim, os membros de nossa equipe dizem que está tudo bem, já que "ContainsKey" não itera na coleção de chaves, mas verifica se a chave está contida por meio do código hash em O(1).Portanto, eles afirmam que não há perigo aqui.

Gostaria de obter suas opiniões honestas sobre esse assunto.

Foi útil?

Solução

Não faça isso.Não é seguro.

Você poderia estar ligando ContainsKey de um thread enquanto outro thread chama Add.Isso simplesmente não é suportado por Dictionary<TKey, TValue>.Se Add precisa realocar buckets etc., posso imaginar você poderia obter alguns resultados muito estranhos ou uma exceção.Isto poderia foram escritos de tal forma que você não vê nenhum efeito desagradável, mas eu não gostaria de confiar nisso.

Uma coisa é usar o bloqueio verificado duas vezes para leituras/gravações simples em um campo, embora eu ainda argumente contra isso - outra é fazer chamadas para uma API que foi explicitamente descrita como não sendo seguro para múltiplas chamadas simultâneas.

Se você estiver no .NET 4, ConcurrentDictionary é provavelmente o caminho a seguir.Caso contrário, basta bloquear todos os acessos.

Outras dicas

Se você estiver em um ambiente multithread, talvez prefira usar um ConcurrentDictionary.Eu escrevi sobre isso há alguns meses, você pode achar o artigo útil: http://colinmackay.co.uk/blog/2011/03/24/parallelisation-in-net-4-0-the-concurrent-dictionary/

Este código está incorreto.O Dictionary<TKey, TValue> type não oferece suporte a operações simultâneas de leitura e gravação.Mesmo que seu Add método é chamado dentro do bloqueio ContainsKey não é.Conseqüentemente, permite facilmente uma violação da regra simultânea de leitura/gravação e levará à corrupção em sua instância

Não parece seguro para threads, mas provavelmente seria difícil fazê-lo falhar.

O argumento iteração vs pesquisa de hash não é válido; pode haver uma colisão de hash, por exemplo.

Se este dicionário raramente é escrito e lido com frequência, geralmente utilizo o bloqueio duplo seguro, substituindo o dicionário inteiro na gravação.Isso é particularmente eficaz se você puder gravar em lote para torná-las menos frequentes.

Por exemplo, esta é uma versão reduzida de um método que usamos que tenta obter um objeto de esquema associado a um tipo e, se não conseguir, segue em frente e cria objetos de esquema para todos os tipos que encontra no mesmo assembly como o tipo especificado para minimizar o número de vezes que o dicionário inteiro precisa ser copiado:

    public static Schema GetSchema(Type type)
    {
        if (_schemaLookup.TryGetValue(type, out Schema schema))
            return schema;

        lock (_syncRoot) {
            if (_schemaLookup.TryGetValue(type, out schema))
                return schema;

            var newLookup = new Dictionary<Type, Schema>(_schemaLookup);

            foreach (var t in type.Assembly.GetTypes()) {
                var newSchema = new Schema(t);
                newLookup.Add(t, newSchema);
            }

            _schemaLookup = newLookup;

            return _schemaLookup[type];
        }
    }

Então o dicionário neste caso será reconstruído, no máximo, quantas vezes houver assemblies com tipos que necessitem de esquemas.Durante o resto da vida útil do aplicativo, os acessos ao dicionário serão livres de bloqueios.A cópia do dicionário torna-se um custo único de inicialização do assembly.A troca de dicionário é segura para threads porque as gravações de ponteiro são atômicas, portanto, toda a referência é trocada de uma só vez.

Você também pode aplicar princípios semelhantes em outras situações.

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