Condividi tramite


Panoramica di BlockingCollection

BlockingCollection<T> è una classe di raccolta thread-safe che fornisce le funzionalità seguenti:

  • Implementazione del modello di Producer-Consumer.

  • Aggiunta e acquisizione simultanea di elementi da più thread.

  • Capacità massima facoltativa.

  • Operazioni di inserimento e rimozione che si fermano quando la raccolta è vuota o piena.

  • Le operazioni di inserimento e rimozione "try" che non causano un blocco o bloccano solo per un periodo di tempo specificato.

  • Racchiude qualsiasi tipo di raccolta che implementa IProducerConsumerCollection<T>

  • Gestione dell'annullamento tramite token di cancellazione.

  • Due tipi di enumerazione con foreach (For Each in Visual Basic):

    1. Enumerazione di sola lettura.

    2. Enumerazione che rimuove gli elementi durante l'enumerazione.

Supporto di delimitazione e blocco

BlockingCollection<T> supporta la delimitazione e il blocco. Vincolo significa che è possibile impostare la capacità massima della raccolta. La delimitazione è importante in certi scenari perché permette di controllare la dimensione massima della raccolta nella memoria e impedisce ai thread produttori di avanzare troppo rispetto ai thread consumatori.

Più thread o attività possono aggiungere elementi alla raccolta contemporaneamente e se la raccolta raggiunge la capacità massima specificata, i thread di produzione si bloccano fino a quando non viene rimosso un elemento. Diversi consumatori possono rimuovere elementi contemporaneamente e, se la collezione diventa vuota, i thread di consumo si bloccheranno fino a quando un produttore non aggiungerà un elemento. Un thread di produzione può chiamare CompleteAdding per indicare che non verranno aggiunti altri elementi. Il consumatore monitora la IsCompleted proprietà per sapere quando la raccolta è vuota e non verranno aggiunti altri elementi. Nell'esempio seguente viene illustrato un oggetto BlockingCollection semplice con una capacità limitata di 100. Un'attività di produzione aggiunge elementi alla raccolta fintanto che una condizione esterna è vera, e quindi chiama CompleteAdding. L'attività del consumatore accetta elementi fino a quando la proprietà IsCompleted è true.

// A bounded collection. It can hold no more
// than 100 items at once.
BlockingCollection<Data> dataItems = new BlockingCollection<Data>(100);

// A simple blocking consumer with no cancellation.
Task.Run(() =>
{
    while (!dataItems.IsCompleted)
    {

        Data data = null;
        // Blocks if dataItems.Count == 0.
        // IOE means that Take() was called on a completed collection.
        // Some other thread can call CompleteAdding after we pass the
        // IsCompleted check but before we call Take.
        // In this example, we can simply catch the exception since the
        // loop will break on the next iteration.
        try
        {
            data = dataItems.Take();
        }
        catch (InvalidOperationException) { }

        if (data != null)
        {
            Process(data);
        }
    }
    Console.WriteLine("\r\nNo more items to take.");
});

// A simple blocking producer with no cancellation.
Task.Run(() =>
{
    while (moreItemsToAdd)
    {
        Data data = GetData();
        // Blocks if numbers.Count == dataItems.BoundedCapacity
        dataItems.Add(data);
    }
    // Let consumer know we are done.
    dataItems.CompleteAdding();
});

' A bounded collection. It can hold no more 
' than 100 items at once.
Dim dataItems = New BlockingCollection(Of Data)(100)

' A simple blocking consumer with no cancellation.
Task.Factory.StartNew(Sub()
                          While dataItems.IsCompleted = False
                              Dim dataItem As Data = Nothing
                              Try
                                  dataItem = dataItems.Take()
                              Catch e As InvalidOperationException
                                  ' IOE means that Take() was called on a completed collection.
                                  ' In this example, we can simply catch the exception since the 
                                  ' loop will break on the next iteration.
                              End Try
                              If (dataItem IsNot Nothing) Then
                                  Process(dataItem)
                              End If
                          End While
                          Console.WriteLine(vbCrLf & "No more items to take.")
                      End Sub)

' A simple blocking producer with no cancellation.
Task.Factory.StartNew(Sub()
                          While moreItemsToAdd = True
                              Dim item As Data = GetData()

                              ' Blocks if dataItems.Count = dataItems.BoundedCapacity.
                              dataItems.Add(item)
                          End While

                          ' Let consumer know we are done.
                          dataItems.CompleteAdding()
                      End Sub)

Per un esempio completo, vedere Procedura: Aggiungere e acquisire elementi singolarmente da un oggetto BlockingCollection.

Operazioni di blocco temporizzato

Durante il blocco delle operazioni a tempo su collezioni delimitate TryAdd e TryTake, il metodo tenta di aggiungere o prelevare un elemento. Se un elemento è disponibile, viene inserito nella variabile passata per riferimento e il metodo restituisce true. Se non viene recuperato alcun elemento dopo un periodo di timeout specificato, il metodo restituisce false. Il thread è quindi libero di eseguire altre operazioni utili prima di riprovare ad accedere alla raccolta. Per un esempio di accesso di blocco temporizzato, vedere il secondo esempio in Procedura: Aggiungere e prendere elementi singolarmente da un oggetto BlockingCollection.

Annullamento delle operazioni di aggiunta e rimozione

Le operazioni Add e Take vengono in genere eseguite in un ciclo. È possibile annullare un ciclo passando un CancellationToken al metodo TryAdd o TryTake e quindi controllando il valore della proprietà IsCancellationRequested del token in ogni iterazione. Se il valore è true, spetta all'utente rispondere alla richiesta di annullamento pulendo le risorse e chiudendo il ciclo. L'esempio seguente mostra un overload di TryAdd che accetta un token di annullamento, e il codice che lo utilizza:

do
{
    // Cancellation causes OCE. We know how to handle it.
    try
    {
        success = bc.TryAdd(itemToAdd, 2, ct);
    }
    catch (OperationCanceledException)
    {
        bc.CompleteAdding();
        break;
    }
    //...
} while (moreItems == true);
Do While moreItems = True
    ' Cancellation causes OCE. We know how to handle it.
    Try
        success = bc.TryAdd(itemToAdd, 2, ct)
    Catch ex As OperationCanceledException
        bc.CompleteAdding()
        Exit Do
    End Try
Loop

Per un esempio di come aggiungere il supporto per l'annullamento, vedere il secondo esempio in Procedura: Aggiungere e acquisire elementi singolarmente da un oggetto BlockingCollection.

Specificare il tipo di raccolta

Quando si crea un BlockingCollection<T>oggetto , è possibile specificare non solo la capacità limitata, ma anche il tipo di raccolta da usare. Ad esempio, è possibile specificare un comportamento ConcurrentQueue<T> per First-In-First-Out (FIFO) o un comportamento ConcurrentStack<T> per Last-In-First-Out (LIFO). È possibile usare qualsiasi classe di raccolta che implementa l'interfaccia IProducerConsumerCollection<T> . Il tipo di raccolta predefinito per BlockingCollection<T> è ConcurrentQueue<T>. Nell'esempio di codice seguente viene illustrato come creare una BlockingCollection<T> di stringhe con capacità pari a 1000 e viene usato un oggetto ConcurrentBag<T>:

Dim bc = New BlockingCollection(Of String)(New ConcurrentBag(Of String()), 1000)  
BlockingCollection<string> bc = new BlockingCollection<string>(new ConcurrentBag<string>(), 1000 );  

Per altre informazioni, vedere Procedura: Aggiungere funzionalità di delimitazione e blocco a una raccolta.

Supporto IEnumerable

BlockingCollection<T> fornisce un metodo GetConsumingEnumerable che consente ai consumatori di utilizzare foreach (For Each in Visual Basic) per rimuovere elementi fino a quando la raccolta non è completata, cioè quando è vuota e non verranno aggiunti altri elementi. Per altre informazioni, vedere Procedura: Usare ForEach per rimuovere elementi in un oggetto BlockingCollection.

Uso di molte BlockingCollection come una

Per gli scenari in cui un consumer deve acquisire elementi da più raccolte contemporaneamente, è possibile creare matrici di BlockingCollection<T> e usare i metodi statici, TakeFromAny ad esempio e AddToAny che verranno aggiunti o accettati da una delle raccolte nella matrice. Se una raccolta è bloccata, il metodo tenta immediatamente un altro finché non ne trova uno in grado di eseguire l'operazione. Per altre informazioni, vedere Procedura: Usare matrici di raccolte di blocco in una pipeline.

Vedere anche