A BlockingCollection áttekintése

BlockingCollection<T> egy szálbiztos gyűjteményosztály, amely a következő funkciókat biztosítja:

  • A Producer-Consumer minta implementálása.

  • Több szál elemeinek egyidejű hozzáadása és felvétele.

  • Nem kötelező maximális kapacitás.

  • Olyan beszúrási és eltávolítási műveletek, amelyek letiltják, ha a gyűjtemény üres vagy megtelt.

  • Olyan beszúrási és eltávolítási "try" műveletek, amelyek nem blokkolnak, vagy csak egy meghatározott ideig blokkolnak.

  • Kapszuláz minden olyan gyűjteménytípust, amely megvalósít IProducerConsumerCollection<T>

  • Lemondás lemondási jogkivonatokkal.

  • Kétféle enumerálás a foreach (For Each Visual Basicben):

    1. Írásvédett felsorolás.

    2. Enumerálás, amely eltávolítja az elemeket az enumerálásuk közben.

Határolókeret és letiltás támogatása

BlockingCollection<T> támogatja a határolást és a blokkolást. A határolókeret azt jelenti, hogy beállíthatja a gyűjtemény maximális kapacitását. Bizonyos helyzetekben a határolókeretezés azért fontos, mert lehetővé teszi a gyűjtemény maximális méretének szabályozását a memóriában, és megakadályozza, hogy a termelő szálak túl messze haladjanak a fogyasztó szálak előtt.

Egyszerre több szál vagy tevékenység is hozzáadhat elemeket a gyűjteményhez, és ha a gyűjtemény eléri a megadott maximális kapacitást, a termelő szálak blokkolva lesznek, amíg el nem távolít egy elemet. Egyszerre több felhasználó is eltávolíthatja az elemeket, és ha a gyűjtemény üressé válik, a fogyasztó szálak blokkolva lesznek, amíg egy gyártó fel nem vesz egy elemet. Egy termelőszál meghívhatja CompleteAdding, hogy jelezze, hogy több elem nem lesz hozzáadva. A felhasználók figyelik a IsCompleted tulajdonságot, hogy megtudják, mikor üres a gyűjtemény, és nem lesz több elem hozzáadva. Az alábbi példa egy 100-ból álló korlátozott kapacitású, egyszerű BlockingCollection-et mutat be. A gyártói feladat addig ad hozzá elemeket a gyűjteményhez, amíg valamilyen külső feltétel teljesül, majd meghívja a CompleteAdding. A fogyasztói tevékenység addig veszi az elemeket, amíg a IsCompleted tulajdonság igaz.

// 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)

Egy teljes példáért lásd: Elemek hozzáadása és felvétele egyenként a BlockingCollectionből.

Időzított blokkolási műveletek

A korlátozott gyűjtemények időzített blokkolása TryAdd és TryTake műveletei esetén a metódus megpróbál elemeket hozzáadni vagy felvenni. Ha egy elem elérhető, az a hivatkozás által átadott változóba kerül, és a metódus igaz értéket ad vissza. Ha a rendszer egy megadott időtúllépési időszak után nem kér le elemet, a metódus hamis értéket ad vissza. A szál ezután szabadon elvégezhet néhány más hasznos munkát, mielőtt újra megpróbálná elérni a gyűjteményt. Az időzítendő blokkolási hozzáférésről a második példát a How to: Add and Take Items Individually from a BlockingCollection (Elemek hozzáadása és felvétele önállóan a BlockingCollectionből) című témakörben talál.

Hozzáadás és átvétel műveletek megszakítása

A hozzáadási és a felvételi műveletek általában ciklusban hajthatók végre. Megszakíthat egy hurkot, ha egy CancellationToken paramétert ad át a TryAdd vagy TryTake metódusnak, majd az egyes iterációkban ellenőrzi a token IsCancellationRequested tulajdonságának értékét. Ha az érték igaz, akkor Önnek kell válaszolnia a lemondási kérelemre az erőforrások megtisztításával és a ciklusból való kilépéssel. Az alábbi példa egy túlterhelést mutat be, amely megszakítási TryAdd tokent használ, valamint az azt használó kódot.

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

A lemondási támogatás hozzáadásának módjáról a második példát a How to: Add and Take Items Individually from a BlockingCollection (Elemek hozzáadása és felvétele önállóan a BlockingCollectionből) című témakörben talál.

A gyűjtemény típusának megadása

Amikor létrehoz egy gyűjteményt BlockingCollection<T>, nemcsak a határolt kapacitást, hanem a használni kívánt gyűjtemény típusát is megadhatja. Megadhat például egy ConcurrentQueue<T> első be-első ki (FIFO) viselkedést, vagy egy ConcurrentStack<T> utolsó be-első ki (LIFO) viselkedést. Bármilyen olyan gyűjteményosztályt használhat, amely implementálja a IProducerConsumerCollection<T> felületet. Az alapértelmezett gyűjteménytípus a BlockingCollection<T> a ConcurrentQueue<T>. Az alábbi példakód bemutatja, hogyan hozhat létre stringekből álló BlockingCollection<T> 1000 kapacitással rendelkező ConcurrentBag<T> használatával.

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

További információkért lásd: Határolókeret- és blokkolási funkciók hozzáadása gyűjteményhez.

IEnumerable-támogatás

BlockingCollection<T> Olyan GetConsumingEnumerable módszert biztosít, amellyel a felhasználók a gyűjtemény befejezéséig használhatják foreach (For Each a Visual Basicben) az elemek eltávolítását, ami azt jelenti, hogy üres, és nem lesz több elem hozzáadva. További információért lásd: ForEach használata a BlockingCollection elemeinek eltávolításához.

Több BlockingCollection felhasználása egyként

Olyan forgatókönyvek esetén, amikor a fogyasztónak egyszerre több gyűjteményből kell elemeket vennie, létrehozhat tömböket, és használhatja az olyan statikus metódusokat, mint például BlockingCollection<T>, TakeFromAny és AddToAny, amelyekkel a tömb bármely gyűjteményéhez hozzáadhat vagy azokból kivehet elemeket. Ha egy gyűjtemény blokkolva van, a metódus azonnal megpróbál egy másikat, amíg meg nem találja a műveletet végrehajtót. További információért lásd Hogyan: Blokkgyűjtemények tömbjeinek használata egy folyamatban.

Lásd még