2010-05-25 16 views
8

Sto cercando di aggiornare le voci in un qualcosa di ConcurrentDictionary come questo:Aggiornamento campi di valori in una ConcurrentDictionary

class Class1 
{ 
    public int Counter { get; set; } 
} 

class Test 
{ 
    private ConcurrentDictionary<int, Class1> dict = 
     new ConcurrentDictionary<int, Class1>(); 

    public void TestIt() 
    { 
     foreach (var foo in dict) 
     { 
      foo.Value.Counter = foo.Value.Counter + 1; // Simplified example 
     } 
    } 
} 

In sostanza ho bisogno di iterare il dizionario e aggiornare un campo su ogni 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 oggetto. Invece, voglio aggiornare un campo sull'oggetto.

Dopo aver letto this blog entry sul blog del team PFX: Forse ho bisogno di utilizzare 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'oggetto 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à Contatore direttamente se possibile.

Le mie ultime ricerche mi hanno portato a Parallel.ForOgni che suona bene 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 thread-safe.

+0

c'è in realtà alcuna concorrenza succede se sempre e solo aggiorna un campo di ogni valore ogni 30 secondi? Sembra un normale dizionario e un ciclo foreach dovrebbe essere tutto ciò di cui hai bisogno. Parallel.ForEach dovrebbe funzionare anche perché non ci sono risorse condivise. – dtb

+0

Mentre il thread di aggiornamento è in esecuzione, gli oggetti aggiuntivi verranno aggiunti e rimossi dal dizionario da altri thread. Inoltre, altre proprietà sull'oggetto verranno aggiornate tramite altri thread in risposta ai messaggi WCF. Quindi sì, c'è un sacco di concorrenza in corso. Grazie per l'aiuto! – rboarman

+0

OK. Quindi la mia risposta persiste. Utilizzare AddOrUpdate/GetOrAdd/TryUpdate ecc per manipolare il contenuto del dizionario; fare un'istantanea del dizionario quando lo itera; bloccare le singole istanze di Class1 quando si cambiano le loro proprietà. – dtb

risposta

12

In primo luogo, per risolvere il 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 :[i.e. counter = Interlocked.Increment(ref counter);] This will break the atomicity. 
    } 
} 

Iterazione giusti valori dovrebbero essere più veloce di iterazione la coppia di valori chiave. [Anche se penso che l'iterazione un elenco di chiavi e di fare i look-up sarà più veloce ancora sul ConcurrentDictionary nella maggior parte delle situazioni.]

class Test 
{ 
    private ConcurrentDictionary<int, Class1> dictionary = new ConcurrentDictionary<int, Class1>(); 

    public void TestIt() 
    { 
     foreach (var foo in dictionary.Values) 
     { 
      foo.Increment(); 
     } 
    } 

    public void TestItParallel() 
    { 
     Parallel.ForEach(dictionary.Values,x=>x.Increment()); 
    } 

} 
3

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 una fotografia istantanea della raccolta e bloccare le risorse condivise (che sono i singoli valori del dizionario):

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

Tuttavia, se si desidera aggiornare il contatore per un chiave specifica, ConcurrentDictionary può aiutare con l'aggiunta di atomicamente 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 infatti sono i casi in cui si desidera sostituire il valore di una data chiave in un ConcurrentDictiona ry. Ma, come hai detto, non vuoi cambiare il valore, vuoi cambiare una proprietà del valore.

+0

Qui è stata postata un'altra soluzione dalla gente di MS: http://social.msdn.microsoft.com/Forums/en-US/parallelextensions/thread/64f0c314-e3eb-457a-9dad-14d13cf06d68 Proverò entrambi i tuoi suggerimenti e i loro e riportare. Grazie ancora. – rboarman

Problemi correlati