Come lavorare con BlockingCollection in C #

Si consideri uno scenario in cui più thread potrebbero leggere e scrivere su una coda. Più specificamente, potresti avere nello stesso momento, più produttori che archiviano i dati e più consumatori che li recuperano da un archivio dati comune. Quindi, avresti bisogno di un meccanismo di sincronizzazione appropriato in modo da sincronizzare l'accesso a questi dati.

Ecco esattamente dove viene in soccorso la classe BlockingCollection. Sebbene ci siano molti altri modi, questa classe fornisce uno dei modi più efficienti per sincronizzare l'accesso ai dati. La classe BlockingCollection appartiene allo spazio dei nomi System.Collections.Concurrent.

Cos'è una BlockingCollection?

BlockingCollection è una raccolta thread-safe in cui è possibile aggiungere e rimuovere dati contemporaneamente da più thread. È rappresentato in .Net tramite la classe BlockingCollection; puoi usare questa classe per implementare un modello produttore-consumatore.

Nel pattern produttore-consumatore, hai due componenti distinti che vengono eseguiti su due thread diversi. Questi includono un componente produttore che produce alcuni dati che vengono inviati alla coda e un consumatore che utilizza i dati archiviati nella coda. Quando si utilizza BlockingCollection, è possibile specificare la capacità limitata e il tipo di raccolta che si desidera utilizzare.

Il tipo BlockingCollection funge da wrapper su un'istanza di tipo IProducerConsumerCollection. In altre parole, funge da wrapper su un'altra raccolta che a sua volta implementa l'interfaccia IProducerConsumerCollection. Ad esempio, le classi ConcurrentBag, ConcurrentQueue e ConcurrentStack possono essere utilizzate con BlockingCollection poiché tutte implementano l'interfaccia IProducerConsumerCollection.

Si noti che l'interfaccia IProducerConsumerCollection contiene una dichiarazione di metodi che possono essere utilizzati per lavorare con raccolte thread-safe. L'MSDN afferma: "Definisce i metodi per manipolare le raccolte thread-safe destinate all'uso produttore / consumatore. Questa interfaccia fornisce una rappresentazione unificata per le raccolte produttore / consumatore in modo che astrazioni di livello superiore come System.Collections.Concurrent.BlockingCollection possano utilizzare la raccolta come il meccanismo di archiviazione sottostante. "

Il frammento di codice seguente mostra come creare un'istanza di BlockingCollection di stringhe.

var blockingCollection = new BlockingCollection();

Quando si utilizza BlockingCollection, è possibile aggiungere dati alla raccolta utilizzando il metodo Add o il metodo TryAdd. Vediamo ora la differenza tra questi due metodi.

BlockingCollection data = new BlockingCollection(boundedCapacity: 3);

data.Add(1);

data.Add(2);

data.Add(3);

data.Add(4); //This would block until an item is removed from the collection.

Nota come abbiamo specificato boundedCapacity durante la creazione di un'istanza di BlockingCollection come mostrato nello snippet di codice riportato sopra. Viene specificato per indicare la dimensione limitata dell'istanza della raccolta.

Puoi anche utilizzare il metodo TryAdd per aggiungere un elemento a un'istanza di BlockingCollection. In questo metodo è possibile utilizzare un valore di timeout. Se l'operazione di aggiunta non riesce entro il tempo specificato, il metodo TryAdd restituisce false. Il frammento di codice seguente mostra come sfruttare il metodo TryAdd per aggiungere un elemento a un'istanza di BlockingCollection.

BlockingCollection data = new BlockingCollection(boundedCapacity: 3);

data.Add(1);

data.Add(2);

data.Add(3);

if (data.TryAdd(4, TimeSpan.FromMilliseconds(100)))

{

   Console.WriteLine("A new item was successfully added to the collection.");

}

else

{

   Console.WriteLine("Failed to add a new item to the collection.");

}

Per rimuovere un elemento da una BlockingCollection, puoi utilizzare il metodo Take o TryTake. Nota che il metodo Take si blocca se non ci sono elementi nella raccolta e si sblocca non appena viene aggiunto un nuovo elemento alla raccolta. Il metodo TryTake può essere utilizzato anche per rimuovere un elemento da un'istanza di BlockingCollection. È possibile specificare un valore di timeout con questo metodo in modo che il metodo si blocchi (fino allo scadere del tempo specificato) finché un elemento non viene aggiunto alla raccolta. Se un elemento non può essere rimosso dalla raccolta durante questo periodo (il timeout specificato), il metodo TryTake restituisce false.

Il frammento di codice seguente illustra come utilizzare il metodo TryTake per rimuovere un elemento da un'istanza di tipo BlockingCollection.

int item;

while (data.TryTake(out item, TimeSpan.FromMilliseconds(100)))

{

   Console.WriteLine(item);

}

Ecco un elenco di codici completo come riferimento. Questo programma illustra come utilizzare BlockingCollection per aggiungere e rimuovere elementi da e verso una raccolta.

 class Program

   {

       private static BlockingCollection data = new BlockingCollection();

       private static void Producer()

       {

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

           {

               data.Add(ctr);

               Thread.Sleep(100);

           }

       }

       private static void Consumer()

       {

           foreach (var item in data.GetConsumingEnumerable())

           {

               Console.WriteLine(item);

           }

       }

       static void Main(string[] args)

       {

           var producer = Task.Factory.StartNew(() => Producer());

           var consumer = Task.Factory.StartNew(() => Consumer());

           Console.Read();

       }

   }