Aggiornamento dei campi di valori in un ConcurrentDictionary

Sto cercando di aggiornare le voci in un ConcurrentDictionary qualcosa del genere:

class Class1 { public int Counter { get; set; } } class Test { private ConcurrentDictionary dict = new ConcurrentDictionary(); public void TestIt() { foreach (var foo in dict) { foo.Value.Counter = foo.Value.Counter + 1; // Simplified example } } } 

Essenzialmente ho bisogno di scorrere il dizionario e aggiornare un campo su ciascun valore. Capisco dalla documentazione che devo evitare di usare la proprietà Value. Invece penso che ho bisogno di usare TryUpdate tranne che non voglio sostituire il mio intero object. Invece, voglio aggiornare un campo sull’object.

Dopo aver letto questo post sul blog del team PFX: Forse ho bisogno di usare AddOrUpdate e semplicemente non fare nulla nell’aggiunta delegato.

Qualcuno ha qualche idea su come farlo?


Ho decine di migliaia di oggetti nel dizionario che devo aggiornare ogni trenta secondi circa. La creazione di nuovi per aggiornare la proprietà probabilmente non è fattibile. Avrei bisogno di clonare l’object esistente, aggiornarlo e sostituire quello nel dizionario. Avrei anche bisogno di bloccarlo per la durata del ciclo clone / add. Che schifo.

Quello che mi piacerebbe fare è iterare sugli oggetti e aggiornare la proprietà Counter direttamente se ansible.

Le mie ultime ricerche mi hanno portato a Parallel.ForOgni che suona alla grande ma non dovrebbe essere usato per azioni che aggiornano lo stato.

Ho anche visto menzionare Interlocked.Increment che suona alla grande ma ho ancora bisogno di capire come usarlo su ogni elemento nel mio dizionario in modo sicuro.

Innanzitutto, per risolvere il tuo problema di blocco:

 class Class1 { // this must be a variable so that we can pass it by ref into Interlocked.Increment. private int counter; public int Counter { get{return counter; } } public void Increment() { // this is about as thread safe as you can get. // From MSDN: Increments a specified variable and stores the result, as an atomic operation. Interlocked.Increment(ref counter); // you can return the result of Increment if you want the new value, //but DO NOT set the counter to the result :[ie counter = Interlocked.Increment(ref counter);] This will break the atomicity. } } 

L’iterazione dei valori giusti dovrebbe essere più veloce dell’iterazione della coppia di valori chiave. [Anche se penso che iterare un elenco di chiavi e fare le ricerche sarà ancora più veloce sul ConcurrentDictionary nella maggior parte delle situazioni.]

 class Test { private ConcurrentDictionary dictionary = new ConcurrentDictionary(); public void TestIt() { foreach (var foo in dictionary.Values) { foo.Increment(); } } public void TestItParallel() { Parallel.ForEach(dictionary.Values,x=>x.Increment() ); } } 

ConcurrentDictionary non ti aiuta ad accedere ai membri dei valori memorizzati contemporaneamente, solo con gli elementi stessi.

Se più thread chiamano TestIt, si dovrebbe ottenere un’istantanea della raccolta e bloccare le risorse condivise (che sono i singoli valori del dizionario):

 foreach (KeyValuePair kvp in dict.ToArray()) { Class1 value = kvp.Value; lock (value) { value.Counter = value.Counter + 1; } } 

Tuttavia, se si desidera aggiornare il contatore per una chiave specifica, ConcurrentDictionary può aiutare con l’aggiunta atomica di una nuova coppia di valori chiave se la chiave non esiste:

 Class1 value = dict.GetOrAdd(42, key => new Class1()); lock (value) { value.Counter = value.Counter + 1; } 

AddOrUpdate e TryUpdate si trovano infatti nei casi in cui si desidera sostituire il valore di una determinata chiave in un ConcurrentDictionary. Ma, come hai detto, non vuoi cambiare il valore, vuoi cambiare una proprietà del valore.