Pergunta

Eu tenho um Dictionary<string, someobject>.

EDIT: Foi apontado para mim, que meu exemplo foi ruim. Toda a minha intenção não era para atualizar as referências em um laço, mas para atualizar valores diferentes com base em tópicos differnt necessidade de atualizar / obter os dados. Mudei o loop para um método.

Eu preciso atualizar itens no meu dicionário - uma tecla de cada vez e eu queria saber se há algum problema em usar o bloqueio sobre o valor .key do meu objeto Dictionary

?
private static Dictionary<string, MatrixElement> matrixElements = new Dictionary<string, MatrixElement>();

//Pseudo-code

public static void UpdateValue(string key)
{
    KeyValuePair<string, MatrixElement> keyValuePair = matrixElements[key];
    lock (keyValuePair.Key)
    {
        keyValuePair.Value  = SomeMeanMethod();
    }
}

Oxalá realizar-se em tribunal ou não? Eu só quero que cada valor no dicionário a ser bloqueado de forma independente de modo bloqueio (e atualização) um valor não bloqueia os outros. Também estou ciente de que o bloqueio vai realizar por um longo tempo -. Mas os dados não será válida até que actualiza totalmente

Foi útil?

Solução

O bloqueio em um objeto que é acessível fora do código de bloqueio é um grande risco. Se qualquer outro código (em qualquer lugar) sempre bloqueia esse objeto que você poderia estar em alguns impasses que são difíceis de depurar. Observe também que você bloquear o objeto , e não o de referência, por isso, se eu te dei um dicionário, eu ainda pode manter referências para as chaves e bloqueio sobre eles - levando-nos a travar no mesmo objeto <. / p>

Se você encapsular completamente o dicionário, e gerar as chaves de si mesmo (eles não são já aconteceu no, então você pode estar seguro.

No entanto, tentar furar a uma regra -. Limite a visibilidade dos objetos que você trava para o próprio código de bloqueio sempre que possível

É por isso que você vê o seguinte:

public class Something
{
  private readonly object lockObj = new object();

  public SomethingReentrant()
  {
    lock(lockObj)    // Line A
    {
      // ...
     }
   }
}

ao invés de ver a linha A acima substituído por

  lock(this)

Assim, um objeto separado é bloqueado, e a visibilidade é limitada.

Editar Jon Skeet observou corretamente que lockObj acima deve ser somente leitura.

Outras dicas

Não, isso não iria funcionar.

A razão é corda internar . Isso significa que:

string a = "Something";
string b = "Something";

são ambos o mesmo objeto! Portanto, você nunca deve bloquear em cordas, porque se alguma outra parte do programa (por exemplo, outra instância do mesmo objeto) também quer bloquear na mesma corda, você pode acidentalmente criar contenção de bloqueio, onde não há necessidade para isso; possivelmente até mesmo um impasse.

Sinta-se livre para fazer isso com os não-cordas, no entanto. Para melhor clareza, eu torná-lo um hábito pessoal para criar sempre um objeto de bloqueio separada:

class Something
{
    bool threadSafeBool = true;
    object threadSafeBoolLock = new object(); // Always lock this to use threadSafeBool
}

Eu recomendo que você faça o mesmo. Criar um dicionário com os objetos de bloqueio para cada célula da matriz. Em seguida, bloquear esses objetos quando necessário.

PS. Alterando a coleção que você está interagindo sobre não é considerado muito bom. Ele vai mesmo lançar uma exceção com a maioria dos tipos de coleção. Tente refazer este - por exemplo, iterar sobre uma lista de chaves, se ele vai ser sempre constante, não os pares.

Nota: Presumo exceção ao modificar coleção durante a iteração já está fixado

dicionário não é thread-safe coleção, o que significa que é não seguro modificar e ler coleção de segmentos diferentes, sem sincronização externa. Hashtable é (era?) Thread-safe para o cenário-muitas-leitores de um escritor, mas Dicionário tem estrutura de dados internos diferentes e não herda esta garantia.

Isto significa que você não pode modificar o dicionário enquanto você acessá-lo para ler ou escrever a partir do outro segmento, ele pode apenas quebrou estruturas de dados internas. Bloqueio na chave não protege estrutura de dados interna, porque enquanto você modificar essa mesma tecla alguém poderia estar lendo diferente chave de seu dicionário em outro segmento. Mesmo se você pode garantir que todas as suas chaves são mesmos objetos (como dito sobre corda internar), isso não trazê-lo no lado seguro. Exemplo:

  1. Você tranca a chave e começar a modificar dicionário
  2. tentativas outro segmento para obter o valor para a chave que acontece de cair no mesmo balde como um bloqueado. Este não é apenas quando hashcodes de dois objetos são o mesmo, mas mais frequentemente quando hashcode% tableSize é o mesmo.
  3. Ambos os segmentos estão acessando o mesmo balde (lista ligada de chaves com o mesmo código hash valor% tableSize)

Se não houver tal chave no dicionário, primeiro segmento vai começar a modificar a lista, eo segundo segmento será propensos a ler estado incompleto.

Se essa chave já existe, detalhes de implementação de dicionário pode ainda modificar a estrutura de dados, por exemplo, mover chaves acessados ??recentemente à cabeça da lista para uma mais rápida recuperação. Você não pode confiar em detalhes de implementação.

Há muitos casos como esse, quando você terá dicionário corrompido. Então você tem que ter objeto de sincronização externo (ou usar o próprio dicionário, se não for exposto ao público) e bloqueio sobre ele durante toda a operação. Se você precisar de fechaduras mais granulares quando a operação pode levar algum tempo, você pode copiar as chaves você precisa de atualização, iterar sobre ele, bloquear dicionário inteiro durante a atualização de chave única (não se esqueça de verificar chave ainda está lá) e liberá-lo para deixar que outras threads executar.

Se não me engano, a intenção inicial era para bloquear em um único elemento, em vez de bloqueio de todo o dicionário (como bloqueio no nível de linha da tabela vs. bloqueio nível em um DB)

Você não pode bloquear a chave do dicionário como muitos aqui explicado.

O que você pode fazer, é manter um dicionário interno de objetos de bloqueio, que corresponde ao dicionário real. Então, quando você gostaria de gravação para YourDictionary [Key1], você primeiro bloquear InternalLocksDictionary [Key1] -. Portanto, apenas um único segmento vai escrever para YourDictionary

Um exemplo (não muito limpo) pode ser encontrada aqui .

Apenas me deparei com isso e pensei share ID de algum código que eu escrevi há alguns anos onde eu precisava de um dicionário em uma base chave

 using (var lockObject = new Lock(hashedCacheID))
 {
    var lockedKey = lockObject.GetLock();
    //now do something with the dictionary
 }

a classe bloqueio

class Lock : IDisposable
    {
        private static readonly Dictionary<string, string> Lockedkeys = new Dictionary<string, string>();

        private static readonly object CritialLock = new object();

        private readonly string _key;
        private bool _isLocked;

        public Lock(string key)
        {
            _key = key;

            lock (CritialLock)
            {
                //if the dictionary doesnt contain the key add it
                if (!Lockedkeys.ContainsKey(key))
                {
                    Lockedkeys.Add(key, String.Copy(key)); //enusre that the two objects have different references
                }
            }
        }

        public string GetLock()
        {
            var key = Lockedkeys[_key];

            if (!_isLocked)
            {
                Monitor.Enter(key);
            }
            _isLocked = true;

            return key;
        }

        public void Dispose()
        {
            var key = Lockedkeys[_key];

            if (_isLocked)
            {
                Monitor.Exit(key);
            }
            _isLocked = false;
        }
    }

No seu exemplo, você não pode fazer o que você quer fazer!

Você vai ter um System.InvalidOperationException com uma mensagem de coleção foi modificada; operação de enumeração não pode executar.

Aqui está um exemplo para provar:

using System.Collections.Generic;
using System;

public class Test
{
    private Int32 age = 42;

    static public void Main()
    {
       (new Test()).TestMethod();
    }

    public void TestMethod()
    {
        Dictionary<Int32, string> myDict = new Dictionary<Int32, string>();

        myDict[age] = age.ToString();

        foreach(KeyValuePair<Int32, string> pair in myDict)
        {
            Console.WriteLine("{0} : {1}", pair.Key, pair.Value);
            ++age;
            Console.WriteLine("{0} : {1}", pair.Key, pair.Value);
            myDict[pair.Key] = "new";
            Console.WriteLine("Changed!");
        }
    }   
}

A saída seria:

42 : 42
42 : 42

Unhandled Exception: System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Collections.Generic.Dictionary`2.Enumerator.MoveNext()
   at Test.TestMethod()
   at Test.Main()

Eu posso ver alguns problemas potenciais lá:

  1. cordas pode ser compartilhado, para que não necessariamente sabem quem mais poderia ser bloqueio no objeto chave para que outra razão
  2. cordas pode não ser compartilhado: você pode ser o bloqueio em uma chave string com o valor "Key1" e algum outro pedaço de código pode ter um objeto de cadeia diferente que também contém os caracteres "Key1" . Para o dicionário eles são a mesma chave, mas tanto quanto bloqueio está em causa são objetos diferentes.
  3. Esse bloqueio não vai impedir alterações ao valor objetos em si, ou seja, matrixElements[someKey].ChangeAllYourContents()
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top