Domanda

Ho un Dictionary < string, someobject > .

EDIT: mi è stato fatto notare che il mio esempio era negativo. La mia intera intenzione non era quella di aggiornare i riferimenti in un ciclo, ma di aggiornare diversi valori basati su thread diversi è necessario aggiornare / ottenere i dati. Ho cambiato il loop in un metodo.

Devo aggiornare gli elementi nel mio dizionario - una chiave alla volta e mi chiedevo se ci fossero problemi nell'uso del blocco sul valore .key del mio oggetto 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();
    }
}

Resta in tribunale o fallirebbe? Voglio solo che ogni valore nel dizionario sia bloccato in modo indipendente, quindi bloccare (e aggiornare) un valore non blocca gli altri. Inoltre sono consapevole che il blocco rimarrà a lungo, ma i dati non saranno validi fino a quando non saranno completamente aggiornati.

È stato utile?

Soluzione

Il blocco su un oggetto accessibile al di fuori del codice è un grosso rischio. Se qualsiasi altro codice (ovunque) blocca mai quell'oggetto, potresti trovarti in alcuni deadlock difficili da eseguire il debug. Nota anche che blocchi oggetto , non il riferimento, quindi se ti fornissi un dizionario, potrei comunque conservare i riferimenti alle chiavi e bloccarli, causandoci di bloccare lo stesso oggetto.

Se incapsuli completamente il dizionario e generi le chiavi tu stesso (non sono mai state inserite, allora potresti essere al sicuro.

Tuttavia, prova a rispettare una regola: limita la visibilità degli oggetti che blocchi al codice di blocco stesso quando possibile.

Ecco perché vedi questo:

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

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

anziché vedere la riga A sopra sostituita da

  lock(this)

In questo modo, un oggetto separato è bloccato e la visibilità è limitata.

Modifica Jon Skeet ha osservato correttamente che lockObj sopra dovrebbe essere di sola lettura.

Altri suggerimenti

No, questo non funzionerebbe.

Il motivo è internamento di stringhe . Ciò significa che:

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

sono entrambi lo stesso oggetto! Pertanto, non si dovrebbe mai bloccare le stringhe perché se un'altra parte del programma (ad esempio un'altra istanza di questo stesso oggetto) desidera bloccare anche la stessa stringa, è possibile creare accidentalmente una contesa di blocco dove non è necessario; forse anche un punto morto.

Sentiti libero di farlo con non stringhe, però. Per maggiore chiarezza, ho l'abitudine personale di creare sempre un oggetto lock separato:

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

Ti consiglio di fare lo stesso. Crea un dizionario con gli oggetti lock per ogni cella matrice. Quindi, bloccare questi oggetti quando necessario.

PS. Cambiare la raccolta su cui stai ripetendo non è considerato molto bello. Genererà anche un'eccezione con la maggior parte dei tipi di raccolta. Prova a refactoring questo - ad es. scorrere su un elenco di chiavi, se sarà sempre costante, non le coppie.

Nota: presumo un'eccezione quando la modifica della raccolta durante l'iterazione è già stata risolta

Il dizionario non è una raccolta thread-safe, il che significa che non è sicuro modificare e leggere la raccolta da thread diversi senza sincronizzazione esterna. Hashtable è (era?) Thread-safe per uno scenario con uno scrittore e molti lettori, ma Dictionary ha una struttura di dati interna diversa e non eredita questa garanzia.

Ciò significa che non è possibile modificare il dizionario mentre si accede a esso per leggere o scrivere dall'altro thread, può solo rompere le strutture di dati interne. Il blocco della chiave non protegge la struttura interna dei dati, perché mentre modifichi quella chiave qualcuno potrebbe leggere una chiave diversa del tuo dizionario in un altro thread. Anche se puoi garantire che tutte le tue chiavi sono gli stessi oggetti (come detto per l'internazionalizzazione delle stringhe), questo non ti porta sul sicuro. Esempio:

  1. Blocchi la chiave e inizi a modificare il dizionario
  2. Un altro thread tenta di ottenere valore per la chiave che capita di cadere nello stesso bucket di quello bloccato. Questo non è solo quando gli hashcode di due oggetti sono uguali, ma più frequentemente quando hashcode% tableSize è lo stesso.
  3. Entrambi i thread accedono allo stesso bucket (elenco collegato di chiavi con lo stesso hashcode% tableSize value)

Se nel dizionario non è presente tale chiave, il primo thread inizierà a modificare l'elenco e il secondo thread probabilmente leggerà lo stato incompleto.

Se tale chiave esiste già, i dettagli di implementazione del dizionario potrebbero comunque modificare la struttura dei dati, ad esempio spostare le chiavi a cui si è avuto accesso di recente in testa all'elenco per un recupero più rapido. Non puoi fare affidamento sui dettagli di implementazione.

Ci sono molti casi del genere, quando avrai un dizionario corrotto. Quindi devi avere un oggetto di sincronizzazione esterno (o usare il Dizionario stesso, se non è esposto al pubblico) e bloccarlo durante l'intera operazione. Se hai bisogno di blocchi più granulari quando l'operazione può richiedere molto tempo, puoi copiare le chiavi che devi aggiornare, iterare su di essa, bloccare l'intero dizionario durante l'aggiornamento della chiave singola (non dimenticare di verificare che la chiave sia ancora lì) e rilasciarla su lascia correre altri thread.

Se non sbaglio, l'intenzione originale era quella di bloccare un singolo elemento, piuttosto che bloccare l'intero dizionario (come blocco a livello di tabella contro blocco a livello di riga in un DB)

non puoi bloccare la chiave del dizionario come molti qui spiegati.

Quello che puoi fare è mantenere un dizionario interno di oggetti di blocco, che corrisponde al dizionario reale. Quindi, quando vuoi scrivere su YourDictionary [Key1], devi prima bloccare InternalLocksDictionary [Key1], quindi solo un singolo thread scriverà su YourDictionary.

un esempio (non troppo pulito) può essere trovato qui .

Mi sono appena imbattuto in questo e ho pensato che condividessi un codice che ho scritto qualche anno fa dove avevo bisogno di un dizionario su base chiave

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

la classe di blocco

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

Nel tuo esempio, non puoi fare quello che vuoi fare!

Otterrai un System.InvalidOperationException con un messaggio di Raccolta modificata; l'operazione di enumerazione potrebbe non essere eseguita.

Ecco un esempio da provare:

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

L'output sarebbe:

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()

Qui posso vedere alcuni potenziali problemi:

  1. le stringhe possono essere condivise, quindi non si sa necessariamente chi altro potrebbe bloccare quell'oggetto chiave per quale altro motivo
  2. le stringhe potrebbero non essere condivise: potresti bloccare una chiave di stringa con il valore " Key1 " e qualche altro pezzo di codice può avere un oggetto stringa diverso che contiene anche i caratteri "Chiave1". Per il dizionario sono la stessa chiave ma per quanto riguarda il blocco sono oggetti diversi.
  3. Il blocco non impedisce le modifiche agli oggetti valore stessi, ad esempio matrixElements[someKey[.ChangeAllYourContents()
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top