Come lavorare con ConcurrentBag e ConcurrentDictionary in .Net

Le raccolte simultanee in .Net sono contenute nello spazio dei nomi System.Collections.Concurrent e forniscono implementazioni prive di blocchi e thread-safe delle classi di raccolta. Le raccolte thread-safe sono state introdotte per la prima volta in .Net 4 e le raccolte sono state introdotte per la prima volta come parte di .Net Framework 1.0 ed erano disponibili nello spazio dei nomi System.Collections.

È possibile sfruttare le raccolte simultanee per lavorare con le raccolte senza dover scrivere codice aggiuntivo per la sincronizzazione dei thread. Puoi dare un'occhiata al mio articolo su ConcurrentStack e ConcurrentQueue.

ConcurrentBag

ConcurrentBag fornisce una raccolta thread-safe di un set di elementi non ordinato. Ecco l'elenco dei metodi importanti della classe ConcurrentBag.

  • Aggiungi (elemento T): questo metodo viene utilizzato per aggiungere un elemento a ConcurrentBag.
  • TryPeek (out T): questo metodo viene utilizzato per recuperare un elemento da ConcurrentBag senza rimuoverlo.
  • TryTake (out T): questo metodo viene utilizzato per recuperare un elemento da ConcurrentBag. Notare che questo metodo rimuove l'elemento dalla raccolta.

Il frammento di codice seguente illustra come creare una raccolta ConcurrentBag e archiviarvi gli elementi.

ConcurrentBag concurrentBag = new ConcurrentBag();

  for (int i = 0; i < 10; i++)

    {

        concurrentBag.Add(i);

    }

Se dovessi recuperare gli elementi nella raccolta, dovresti scrivere il seguente codice:

while (concurrentBag.Count > 0)

  {

      Int32 element;

      if (concurrentBag.TryTake(out element))

       {

         Console.WriteLine(element);

       }

  }

Nota come è stato utilizzato il metodo TryTake: restituisce true in caso di successo, false in caso contrario. Il metodo TryTake rimuove anche l'elemento dalla raccolta. Il ciclo while continua l'esecuzione fino al momento in cui il conteggio degli elementi nella raccolta è maggiore di zero. Ecco l'elenco completo del codice come riferimento.

static void Main(string[] args)

        {

            ConcurrentBag concurrentBag = new ConcurrentBag();

            for (int i = 0; i < 10; i++)

            {

                concurrentBag.Add(i);

            }

            while (concurrentBag.Count > 0)

            {

                Int32 element;

                if (concurrentBag.TryTake(out element))

                {

                    Console.WriteLine(element);

                }

            }

            Console.Read();

        }

ConcurrentDictionary

Un dizionario è una raccolta generica di coppie chiave / valore. È più veloce di un Hashtable in quanto elimina le spese generali di boxe e un-boxing. ConcurrentDictionary è contenuto nello spazio dei nomi System.Collections.Concurrent e rappresenta un dizionario thread-safe.

I membri importanti della classe ConcurrentDictionary includono quanto segue:

  • TryAdd: questo metodo viene utilizzato per aggiungere un elemento nell'istanza ConcurrentDictionary. Si noti che questo metodo genera un'eccezione se la chiave è già presente nella raccolta.
  • TryGetValue: questo metodo viene utilizzato per recuperare un elemento dalla raccolta.
  • TryRemove: questo metodo viene utilizzato per rimuovere un elemento dalla raccolta.
  • TryUpdate: questo metodo viene utilizzato per aggiornare una particolare chiave nell'istanza ConcurrentDictionary con il nuovo valore fornito.

Il frammento di codice seguente mostra come creare un'istanza ConcurrentDictionary e aggiungervi elementi:

ConcurrentDictionary obj = new ConcurrentDictionary();

obj.TryAdd("X001", "This is the first value.");

obj.TryAdd("X002", "This is the second value.");

Se ora provi ad aggiungere un altro elemento ma con la stessa chiave, non riesce. Fare riferimento allo snippet di codice di seguito.

bool success = obj.TryAdd("X002", "This is the third value.");

Il valore della variabile di successo è "false" poiché il tentativo di aggiungere un valore con la stessa chiave fallisce.

Il frammento di codice seguente illustra come recuperare un elemento dalla raccolta in base a una chiave.

string item = null;

bool isExist = obj.TryGetValue("X001", out item);

Se dovessi recuperare tutti gli elementi nella raccolta, potresti invece utilizzare il seguente frammento di codice.

foreach(var v in obj)

    {

        Console.WriteLine(v.Key + "---" + v.Value);

    }

Lo snippet di codice seguente mostra come rimuovere un elemento dalla raccolta.

string item = null;

bool result = obj.TryRemove("X001", out item);

Se dovessi rimuovere tutti gli elementi, potresti utilizzare il seguente frammento di codice.

obj.Clear();

Ora, considera i seguenti due metodi statici.

static void FirstTask(ConcurrentDictionary obj)

        {

            for (int i = 0; i < 10; ++i)

            {

                obj.TryAdd(i.ToString(), i.ToString());

                Thread.Sleep(100);

            }

        }

        static void SecondTask(ConcurrentDictionary obj)

        {

            Thread.Sleep(1000);

            foreach (var item in obj)

            {

                Console.WriteLine("Key: "+item.Key + "   Value: " + item.Value);

                Thread.Sleep(100);

            }

        }

Ecco come eseguire i due metodi precedenti su due istanze di Task contemporaneamente: uno per memorizzare i valori nella raccolta e l'altro per leggere i valori dalla raccolta.

ConcurrentDictionary obj = new ConcurrentDictionary();

Task firstTask = Task.Run(() => FirstTask(obj));           

Task secondTask = Task.Run(() => SecondTask(obj));           

try

{

  Task.WaitAll(firstTask, secondTask);

}

catch (AggregateException ex)

{

   //Write your own code here to handle exception

}

Se esegui il codice precedente, l'eccezione non verrà generata poiché la raccolta qui è thread-safe.