Megosztás a következőn keresztül:


System.Threading.ReaderWriterLockSlim osztály

Ez a cikk kiegészítő megjegyzéseket tartalmaz az API referenciadokumentációjához.

Olyan erőforrások védelmére használható ReaderWriterLockSlim , amelyeket több szál olvas, és amelyeket egyszerre egy szál ír. ReaderWriterLockSlim lehetővé teszi, hogy több szál legyen olvasási módban, lehetővé teszi, hogy egy szál írási módban legyen a zárolás kizárólagos tulajdonjogával, és lehetővé teszi, hogy az olvasási hozzáféréssel rendelkező szál frissíthető olvasási módban legyen, amelyről a szál anélkül frissíthet írási módra, hogy vissza kellene adni az erőforrás olvasási hozzáférését.

Feljegyzés

  • ReaderWriterLockSlimReaderWriterLockhasonló, de egyszerűsített szabályokkal rendelkezik a rekurzióra, valamint a zárolási állapot frissítésére és visszaminősítésére. ReaderWriterLockSlim elkerüli a potenciális holtpont számos előfordulását. Emellett a teljesítmény ReaderWriterLockSlim jelentősen jobb, mint ReaderWriterLock. ReaderWriterLockSlim minden új fejlesztéshez ajánlott.
  • ReaderWriterLockSlim nem biztonságos a szál megszakítása. Ne használja olyan környezetben, ahol a hozzá hozzáférő szálak megszakíthatók, például .NET-keretrendszer. Ha .NET Core-t vagy .NET 5+-ot használ, annak rendben kell lennie. Abort a .NET Core nem támogatja, és elavult a .NET 5-ös és újabb verzióiban.

Alapértelmezés szerint az új példányok ReaderWriterLockSlim a jelölővel jönnek létre, és nem engedélyezik a LockRecursionPolicy.NoRecursion rekurziót. Ez az alapértelmezett szabályzat minden új fejlesztéshez ajánlott, mivel a rekurzió szükségtelen bonyodalmakat okoz, és a kód hajlamosabbá válik a holtpontokra. A meglévő projektekből MonitorReaderWriterLockvaló migrálás egyszerűsítése érdekében a jelölő használatával LockRecursionPolicy.SupportsRecursion létrehozhat olyan példányokat ReaderWriterLockSlim , amelyek lehetővé teszik a rekurziót.

A szálak három módban léphetnek be a zárolásba: olvasási mód, írási mód és frissíthető olvasási mód. (A témakör többi részében a "frissíthető olvasási mód" a "frissíthető mód", az "enter x mode" kifejezés pedig a hosszabb "enter the lock in x mode" kifejezés helyett használatos.)

A rekurziós szabályzattól függetlenül bármikor csak egy szál lehet írási módban. Ha egy szál írási módban van, más szál nem léphet be a zárolásba semmilyen módban. Egyszerre csak egy szál lehet frissíthető módban. Tetszőleges számú szál lehet olvasási módban, és egy szál frissíthető módban, míg a többi szál olvasási módban van.

Fontos

Ez a típus implementálja az interfészt IDisposable . Ha befejezte a típus használatát, közvetlenül vagy közvetve kell megsemmisítenie. A típus közvetlen megsemmisítéséhez hívja meg a metódust Dispose egy try/catch blokkban. Közvetett módon történő elidegenítéséhez használjon olyan nyelvi szerkezetet, mint using a (C#-ban) vagy Using a (Visual Basicben). További információ: "Az IDisposable-t megvalósító objektum használata" című szakasz a IDisposable felület témakörében.

ReaderWriterLockSlim felügyelt szál affinitással rendelkezik; vagyis minden Thread objektumnak saját metódushívásokat kell végrehajtania a zárolási módok be- és kilépéséhez. Egyetlen szál sem módosíthatja egy másik szál módját.

Ha egy ReaderWriterLockSlim szál nem engedélyezi a rekurziót, a zárolást megkísérlő szál több okból is blokkolható:

  • Olyan szál, amely olvasási módú blokkokat próbál megadni, ha vannak olyan szálak, amelyek írási módra várnak, vagy ha egyetlen szál van írási módban.

    Feljegyzés

    Az írók várólistára helyezésekor az új olvasók blokkolása olyan zárolási méltányossági szabályzat, amely az írókat részesíti előnyben. A jelenlegi méltányossági politika kiegyensúlyozza az olvasók és írók méltányosságát, hogy előmozdítsa az átviteli sebességet a leggyakoribb forgatókönyvekben. A .NET jövőbeli verziói új méltányossági szabályzatokat vezethetnek be.

  • Egy olyan szál, amely frissíthető módú blokkokat próbál meg megadni, ha már van egy szál frissíthető módban, ha vannak olyan szálak, amelyek írási módba való belépésre várnak, vagy ha egyetlen szál van írási módban.

  • Olyan szál, amely írási módú blokkokat próbál megadni, ha a három mód bármelyikében van szál.

Frissítési és visszalépési zárolások

A frissíthető mód olyan esetekben használható, amikor egy szál általában a védett erőforrásból olvas, de bizonyos feltételek teljesülése esetén írásra lehet szükség. A frissíthető módban megadott ReaderWriterLockSlim szál olvasási hozzáféréssel rendelkezik a védett erőforráshoz, és a metódusok TryEnterWriteLock meghívásával EnterWriteLock írási módra frissíthet. Mivel egyszerre csak egy szál lehet frissíthető módban, az írási módra való frissítés nem lehet holtpont, ha a rekurzió nem engedélyezett, ami az alapértelmezett szabályzat.

Fontos

A rekurziós szabályzattól függetlenül az eredetileg olvasási módba beírt szálak nem frissíthetők frissíthető módra vagy írási módra, mivel ez a minta nagy valószínűséggel holtpontot hoz létre. Ha például két szál olvasási módban próbál meg írási módba lépni, akkor holtpontra kerülnek. A frissíthető mód úgy van kialakítva, hogy elkerülje az ilyen holtpontok.

Ha más szálak is vannak olvasási módban, a frissítő szál blokkokat ad meg. Amíg a szál le van tiltva, a többi, olvasási módba lépő szál le lesz tiltva. Ha az összes szál kilépett az olvasási módból, a letiltott frissíthető szál írási módba lép. Ha más szálak is írási módra várnak, azok blokkolva maradnak, mivel a frissíthető módban lévő egyetlen szál megakadályozza, hogy kizárólagos hozzáférést kapjanak az erőforráshoz.

Amikor a frissíthető módban lévő szál kilép az írási módból, más, olvasási módra várakozó szálak is megtehetik ezt, kivéve, ha vannak olyan szálak, amelyek írási módra várnak. A frissíthető módban lévő szál korlátlan ideig frissíthető és visszafejthető, feltéve, hogy ez az egyetlen szál, amely a védett erőforrásba ír.

Fontos

Ha több szál írási vagy frissíthető üzemmódba való belépését engedélyezi, nem hagyhatja, hogy egy szál monopolizálja a frissíthető módot. Ellenkező esetben a közvetlenül írási módba lépő szálak határozatlan időre le lesznek tiltva, és amíg le vannak tiltva, a többi szál nem tud bemenni olvasási módba.

A frissíthető módban lévő szálak olvasási módra válthatnak, ha először meghívják a EnterReadLock metódust, majd meghívják a metódust ExitUpgradeableReadLock . Ez a visszalépési minta minden zárolási rekurziós házirend esetében engedélyezett, még akkor is NoRecursion.

Az olvasási módra való visszalépés után a szál csak akkor tudja újraismétleni a frissíthető módot, ha ki nem lépett az olvasási módból.

Adja meg a rekurzív zárolást

A rekurzív zárolási bejegyzést a zárolási szabályzatot meghatározó konstruktor használatával ReaderWriterLockSlim(LockRecursionPolicy) és a beállítás megadásával LockRecursionPolicy.SupportsRecursionhozhatja létreReaderWriterLockSlim.

Feljegyzés

A rekurzió használata nem ajánlott az új fejlesztéshez, mivel szükségtelen bonyodalmakat okoz, és a kód hajlamosabbá válik a holtpontokra.

ReaderWriterLockSlim A rekurziót lehetővé tevő módok esetében a következők adhatók meg a szál által megadható módokról:

  • Az olvasási módban lévő szálak rekurzív módon léphetnek be olvasási módba, de írási vagy frissíthető módba nem léphetnek be. Ha megpróbálja ezt megtenni, a LockRecursionException dobás. Az olvasási mód beírása, majd az írási mód vagy a frissíthető mód megadása olyan minta, amely nagy valószínűséggel holtpontot jelent, ezért nem engedélyezett. A korábban ismertetett módon frissíthető mód érhető el azokban az esetekben, amikor szükség van a zárolás frissítésére.

  • A frissíthető módban lévő szálak írási és/vagy olvasási módba léphetnek, és rekurzív módon beírhatják a három mód bármelyikét. Ha azonban olvasási módban más szálak is vannak, írási módú blokkokat próbál meg megadni.

  • Az írási módban lévő szálak olvasási és/vagy frissíthető módba léphetnek, és rekurzív módon beírhatják a három mód bármelyikét.

  • Egy szál, amely nem lépett be a zárolásba, bármilyen módba léphet. Ez a kísérlet ugyanazért blokkolható, mint egy nem rekurzív zárolási kísérlet.

A szál bármilyen sorrendben kiléphet az általa megadott módokból, feltéve, hogy pontosan annyiszor lép ki az egyes üzemmódokból, mint amennyi az adott módba lépett. Ha egy szál túl sokszor próbál kilépni egy módból, vagy egy olyan módból próbál kilépni, amelyet nem adott meg, SynchronizationLockException a leszakad.

Zárolási állapotok

Hasznos lehet, ha a zárolást az állapotában tekinti. A ReaderWriterLockSlim négy állapot egyikében lehet: nincs beírva, olvasni, frissíteni és írni.

  • Nincs megadva: Ebben az állapotban egyetlen szál sem lépett be a zárolásba (vagy az összes szál kilépett a zárolásból).

  • Olvasás: Ebben az állapotban egy vagy több szál belépett a zárolásba a védett erőforráshoz való olvasási hozzáféréshez.

    Feljegyzés

    A szálak olvasási módban, vagy TryEnterReadLock a frissíthető módról való leminősítéssel léphetnek be a EnterReadLock zárolásba.

  • Frissítés: Ebben az állapotban egy szál belépett az olvasási hozzáférés zárolásába az írási hozzáférésre való frissítés lehetőségével (azaz frissíthető módban), és az olvasási hozzáféréshez nulla vagy több szál lépett be a zárolásba. Egyszerre legfeljebb egy szál léphet be a zárolásba a frissítés lehetőségével; további, frissíthető módba lépő szálak le lesznek tiltva.

  • Írás: Ebben az állapotban egy szál belépett a védett erőforráshoz való írási hozzáférés zárolásába. Az a szál kizárólagos tulajdona a zárnak. Minden más szál, amely bármilyen okból megpróbál belépni a zárolásba, le lesz tiltva.

Az alábbi táblázat a zárolási állapotok közötti áttűnéseket ismerteti a rekurziót nem lehetővé tevő zárolások esetében, amikor egy szál t végrehajtja a bal szélső oszlopban leírt műveletet. A művelet t végrehajtásakor nincs mód. (A frissíthető módban lévő speciális esetet t a táblázat lábjegyzeteiben ismertetjük.) A felső sor a zárolás kezdő állapotát írja le. A cellák leírják, mi történik a szállal, és zárójelben jelenítik meg a zárolási állapot változásait.

Váltás Nincs beírva (N) Olvasás (R) Frissítés (U) Írás (W)
t beírja az olvasási módot t enters (R). t blokkok, ha a szálak írási módra várnak; t ellenkező esetben beírja. t blokkok, ha a szálak írási módra várnak; t ellenkező esetben beírja.1 t Blokkok.
t frissíthető üzemmódot ad meg t enters (U). t blokkok, ha a szálak írási módra vagy frissítési módra várnak; t ellenkező esetben az (U) értéket adja meg. t Blokkok. t Blokkok.
t írási módot ad meg t enters (W). t Blokkok. t Blokkok.2 t Blokkok.

1 Ha t frissíthető módban indul el, olvasási módba lép. Ez a művelet soha nem blokkolja a műveletet. A zárolási állapot nem változik. (A szál ezután befejezhet egy leminősítést olvasási módra a frissíthető módból való kilépéssel.)

2 Ha t frissíthető módban indul el, letiltja, ha olvasási módban vannak szálak. Ellenkező esetben írási módra frissít. A zárolási állapot írásra (W) változik. Ha t letiltja, mert olvasási módban vannak szálak, az írási módba lép, amint az utolsó szál kilép az olvasási módból, még akkor is, ha vannak olyan szálak, amelyek írási módra várnak.

Ha állapotváltozás történik, mert egy szál kilép a zárolásból, a következő felébresztendő szál a következőképpen lesz kiválasztva:

  • Először is, egy olyan szál, amely írási módra vár, és már frissíthető módban van (legfeljebb egy ilyen szál lehet).
  • Ennek hiányában egy olyan szál, amely írási módra vár.
  • Ennek hiányában egy olyan szál, amely frissíthető módra vár.
  • Ennek hiányában minden olyan szál, amely olvasási módra vár.

A zárolás következő állapota mindig Írás (W) lesz az első két esetben, a harmadik esetben pedig frissítés (U), függetlenül a zárolás állapotától, amikor a kilépési szál aktiválta az állapotváltozást. Az utolsó esetben a zárolás állapota Frissítés (U), ha az állapotváltozás után frissíthető módban van egy szál, és az Olvasás (R) egyébként, a korábbi állapottól függetlenül.

Példák

Az alábbi példa egy egyszerű szinkronizált gyorsítótárat mutat be, amely egész számbillentyűkkel rendelkező sztringeket tartalmaz. A rendszer egy példányt ReaderWriterLockSlim használ a belső gyorsítótárként szolgáló hozzáférés Dictionary<TKey,TValue> szinkronizálására.

A példa egyszerű metódusokat tartalmaz a gyorsítótárhoz való hozzáadáshoz, a gyorsítótárból való törléshez és a gyorsítótárból való olvasáshoz. Az időtúllépések szemléltetéséhez a példa tartalmaz egy metódust, amely csak akkor ad hozzá a gyorsítótárhoz, ha azt egy megadott időkorláton belül meg tudja tenni.

A frissíthető mód bemutatásához a példa tartalmaz egy metódust, amely lekéri a kulcshoz társított értéket, és összehasonlítja azt egy új értékkel. Ha az érték nem változik, a metódus olyan állapotot ad vissza, amely nem változik. Ha nem található érték a kulcshoz, a rendszer beszúrja a kulcsot/értékpárt. Ha az érték megváltozott, az frissül. A frissíthető mód lehetővé teszi, hogy a szál szükség szerint frissítsen az olvasási hozzáférésről az írási hozzáférésre, holtpontok kockázata nélkül.

A példa tartalmaz egy beágyazott számbavételt, amely megadja a frissíthető módot bemutató metódus visszatérési értékeit.

A példa a paraméter nélküli konstruktort használja a zárolás létrehozásához, ezért a rekurzió nem engedélyezett. A programozás ReaderWriterLockSlim egyszerűbb és kevésbé hajlamos a hibákra, ha a zárolás nem teszi lehetővé a rekurziót.

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
public class SynchronizedCache 
{
    private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
    private Dictionary<int, string> innerCache = new Dictionary<int, string>();

    public int Count
    { get { return innerCache.Count; } }

    public string Read(int key)
    {
        cacheLock.EnterReadLock();
        try
        {
            return innerCache[key];
        }
        finally
        {
            cacheLock.ExitReadLock();
        }
    }

    public void Add(int key, string value)
    {
        cacheLock.EnterWriteLock();
        try
        {
            innerCache.Add(key, value);
        }
        finally
        {
            cacheLock.ExitWriteLock();
        }
    }

    public bool AddWithTimeout(int key, string value, int timeout)
    {
        if (cacheLock.TryEnterWriteLock(timeout))
        {
            try
            {
                innerCache.Add(key, value);
            }
            finally
            {
                cacheLock.ExitWriteLock();
            }
            return true;
        }
        else
        {
            return false;
        }
    }

    public AddOrUpdateStatus AddOrUpdate(int key, string value)
    {
        cacheLock.EnterUpgradeableReadLock();
        try
        {
            string result = null;
            if (innerCache.TryGetValue(key, out result))
            {
                if (result == value)
                {
                    return AddOrUpdateStatus.Unchanged;
                }
                else
                {
                    cacheLock.EnterWriteLock();
                    try
                    {
                        innerCache[key] = value;
                    }
                    finally
                    {
                        cacheLock.ExitWriteLock();
                    }
                    return AddOrUpdateStatus.Updated;
                }
            }
            else
            {
                cacheLock.EnterWriteLock();
                try
                {
                    innerCache.Add(key, value);
                }
                finally
                {
                    cacheLock.ExitWriteLock();
                }
                return AddOrUpdateStatus.Added;
            }
        }
        finally
        {
            cacheLock.ExitUpgradeableReadLock();
        }
    }

    public void Delete(int key)
    {
        cacheLock.EnterWriteLock();
        try
        {
            innerCache.Remove(key);
        }
        finally
        {
            cacheLock.ExitWriteLock();
        }
    }

    public enum AddOrUpdateStatus
    {
        Added,
        Updated,
        Unchanged
    };

    ~SynchronizedCache()
    {
       if (cacheLock != null) cacheLock.Dispose();
    }
}
Public Class SynchronizedCache
    Private cacheLock As New ReaderWriterLockSlim()
    Private innerCache As New Dictionary(Of Integer, String)

    Public ReadOnly Property Count As Integer
       Get
          Return innerCache.Count
       End Get
    End Property
    
    Public Function Read(ByVal key As Integer) As String
        cacheLock.EnterReadLock()
        Try
            Return innerCache(key)
        Finally
            cacheLock.ExitReadLock()
        End Try
    End Function

    Public Sub Add(ByVal key As Integer, ByVal value As String)
        cacheLock.EnterWriteLock()
        Try
            innerCache.Add(key, value)
        Finally
            cacheLock.ExitWriteLock()
        End Try
    End Sub

    Public Function AddWithTimeout(ByVal key As Integer, ByVal value As String, _
                                   ByVal timeout As Integer) As Boolean
        If cacheLock.TryEnterWriteLock(timeout) Then
            Try
                innerCache.Add(key, value)
            Finally
                cacheLock.ExitWriteLock()
            End Try
            Return True
        Else
            Return False
        End If
    End Function

    Public Function AddOrUpdate(ByVal key As Integer, _
                                ByVal value As String) As AddOrUpdateStatus
        cacheLock.EnterUpgradeableReadLock()
        Try
            Dim result As String = Nothing
            If innerCache.TryGetValue(key, result) Then
                If result = value Then
                    Return AddOrUpdateStatus.Unchanged
                Else
                    cacheLock.EnterWriteLock()
                    Try
                        innerCache.Item(key) = value
                    Finally
                        cacheLock.ExitWriteLock()
                    End Try
                    Return AddOrUpdateStatus.Updated
                End If
            Else
                cacheLock.EnterWriteLock()
                Try
                    innerCache.Add(key, value)
                Finally
                    cacheLock.ExitWriteLock()
                End Try
                Return AddOrUpdateStatus.Added
            End If
        Finally
            cacheLock.ExitUpgradeableReadLock()
        End Try
    End Function

    Public Sub Delete(ByVal key As Integer)
        cacheLock.EnterWriteLock()
        Try
            innerCache.Remove(key)
        Finally
            cacheLock.ExitWriteLock()
        End Try
    End Sub

    Public Enum AddOrUpdateStatus
        Added
        Updated
        Unchanged
    End Enum

    Protected Overrides Sub Finalize()
       If cacheLock IsNot Nothing Then cacheLock.Dispose()
    End Sub
End Class

Az alábbi kód ezután az objektummal tárolja a SynchronizedCache zöldségnevek szótárát. Három feladatot hoz létre. Az első a tömbben tárolt zöldségek nevét írja egy SynchronizedCache példányba. A második és a harmadik feladat a zöldségek nevét jeleníti meg, az elsőt növekvő sorrendben (az alacsony indextől a magas indexig), a másodikat csökkenő sorrendben. Az utolsó feladat megkeresi az "uborka" sztringet, és amikor megtalálja, meghívja a metódust a EnterUpgradeableReadLock "zöld bab" sztring helyére.

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
public class Example
{
   public static void Main()
   {
      var sc = new SynchronizedCache();
      var tasks = new List<Task>();
      int itemsWritten = 0;

      // Execute a writer.
      tasks.Add(Task.Run( () => { String[] vegetables = { "broccoli", "cauliflower",
                                                          "carrot", "sorrel", "baby turnip",
                                                          "beet", "brussel sprout",
                                                          "cabbage", "plantain",
                                                          "spinach", "grape leaves",
                                                          "lime leaves", "corn",
                                                          "radish", "cucumber",
                                                          "raddichio", "lima beans" };
                                  for (int ctr = 1; ctr <= vegetables.Length; ctr++)
                                     sc.Add(ctr, vegetables[ctr - 1]);

                                  itemsWritten = vegetables.Length;
                                  Console.WriteLine("Task {0} wrote {1} items\n",
                                                    Task.CurrentId, itemsWritten);
                                } ));
      // Execute two readers, one to read from first to last and the second from last to first.
      for (int ctr = 0; ctr <= 1; ctr++) {
         bool desc = ctr == 1;
         tasks.Add(Task.Run( () => { int start, last, step;
                                     int items;
                                     do {
                                        String output = String.Empty;
                                        items = sc.Count;
                                        if (! desc) {
                                           start = 1;
                                           step = 1;
                                           last = items;
                                        }
                                        else {
                                           start = items;
                                           step = -1;
                                           last = 1;
                                        }

                                        for (int index = start; desc ? index >= last : index <= last; index += step)
                                           output += String.Format("[{0}] ", sc.Read(index));

                                        Console.WriteLine("Task {0} read {1} items: {2}\n",
                                                          Task.CurrentId, items, output);
                                     } while (items < itemsWritten | itemsWritten == 0);
                             } ));
      }
      // Execute a red/update task.
      tasks.Add(Task.Run( () => { Thread.Sleep(100);
                                  for (int ctr = 1; ctr <= sc.Count; ctr++) {
                                     String value = sc.Read(ctr);
                                     if (value == "cucumber")
                                        if (sc.AddOrUpdate(ctr, "green bean") != SynchronizedCache.AddOrUpdateStatus.Unchanged)
                                           Console.WriteLine("Changed 'cucumber' to 'green bean'");
                                  }
                                } ));

      // Wait for all three tasks to complete.
      Task.WaitAll(tasks.ToArray());

      // Display the final contents of the cache.
      Console.WriteLine();
      Console.WriteLine("Values in synchronized cache: ");
      for (int ctr = 1; ctr <= sc.Count; ctr++)
         Console.WriteLine("   {0}: {1}", ctr, sc.Read(ctr));
   }
}
// The example displays the following output:
//    Task 1 read 0 items:
//
//    Task 3 wrote 17 items
//
//
//    Task 1 read 17 items: [broccoli] [cauliflower] [carrot] [sorrel] [baby turnip] [
//    beet] [brussel sprout] [cabbage] [plantain] [spinach] [grape leaves] [lime leave
//    s] [corn] [radish] [cucumber] [raddichio] [lima beans]
//
//    Task 2 read 0 items:
//
//    Task 2 read 17 items: [lima beans] [raddichio] [cucumber] [radish] [corn] [lime
//    leaves] [grape leaves] [spinach] [plantain] [cabbage] [brussel sprout] [beet] [b
//    aby turnip] [sorrel] [carrot] [cauliflower] [broccoli]
//
//    Changed 'cucumber' to 'green bean'
//
//    Values in synchronized cache:
//       1: broccoli
//       2: cauliflower
//       3: carrot
//       4: sorrel
//       5: baby turnip
//       6: beet
//       7: brussel sprout
//       8: cabbage
//       9: plantain
//       10: spinach
//       11: grape leaves
//       12: lime leaves
//       13: corn
//       14: radish
//       15: green bean
//       16: raddichio
//       17: lima beans
Public Module Example
   Public Sub Main()
      Dim sc As New SynchronizedCache()
      Dim tasks As New List(Of Task)
      Dim itemsWritten As Integer
      
      ' Execute a writer.
      tasks.Add(Task.Run( Sub()
                             Dim vegetables() As String = { "broccoli", "cauliflower",
                                                            "carrot", "sorrel", "baby turnip",
                                                            "beet", "brussel sprout",
                                                            "cabbage", "plantain",
                                                            "spinach", "grape leaves",
                                                            "lime leaves", "corn",
                                                            "radish", "cucumber",
                                                            "raddichio", "lima beans" }
                             For ctr As Integer = 1 to vegetables.Length
                                sc.Add(ctr, vegetables(ctr - 1))
                             Next
                             itemsWritten = vegetables.Length
                             Console.WriteLine("Task {0} wrote {1} items{2}",
                                               Task.CurrentId, itemsWritten, vbCrLf)
                          End Sub))
      ' Execute two readers, one to read from first to last and the second from last to first.
      For ctr As Integer = 0 To 1
         Dim flag As Integer = ctr
         tasks.Add(Task.Run( Sub()
                                Dim start, last, stp As Integer
                                Dim items As Integer
                                Do
                                   Dim output As String = String.Empty
                                   items = sc.Count
                                   If flag = 0 Then
                                      start = 1 : stp = 1 : last = items
                                   Else
                                      start = items : stp = -1 : last = 1
                                   End If
                                   For index As Integer = start To last Step stp
                                      output += String.Format("[{0}] ", sc.Read(index))
                                   Next
                                   Console.WriteLine("Task {0} read {1} items: {2}{3}",
                                                           Task.CurrentId, items, output,
                                                           vbCrLf)
                                Loop While items < itemsWritten Or itemsWritten = 0
                             End Sub))
      Next
      ' Execute a red/update task.
      tasks.Add(Task.Run( Sub()
                             For ctr As Integer = 1 To sc.Count
                                Dim value As String = sc.Read(ctr)
                                If value = "cucumber" Then
                                   If sc.AddOrUpdate(ctr, "green bean") <> SynchronizedCache.AddOrUpdateStatus.Unchanged Then
                                      Console.WriteLine("Changed 'cucumber' to 'green bean'")
                                   End If
                                End If
                             Next
                          End Sub ))

      ' Wait for all three tasks to complete.
      Task.WaitAll(tasks.ToArray())

      ' Display the final contents of the cache.
      Console.WriteLine()
      Console.WriteLine("Values in synchronized cache: ")
      For ctr As Integer = 1 To sc.Count
         Console.WriteLine("   {0}: {1}", ctr, sc.Read(ctr))
      Next
   End Sub
End Module
' The example displays output like the following:
'    Task 1 read 0 items:
'
'    Task 3 wrote 17 items
'
'    Task 1 read 17 items: [broccoli] [cauliflower] [carrot] [sorrel] [baby turnip] [
'    beet] [brussel sprout] [cabbage] [plantain] [spinach] [grape leaves] [lime leave
'    s] [corn] [radish] [cucumber] [raddichio] [lima beans]
'
'    Task 2 read 0 items:
'
'    Task 2 read 17 items: [lima beans] [raddichio] [cucumber] [radish] [corn] [lime
'    leaves] [grape leaves] [spinach] [plantain] [cabbage] [brussel sprout] [beet] [b
'    aby turnip] [sorrel] [carrot] [cauliflower] [broccoli]
'
'    Changed 'cucumber' to 'green bean'
'
'    Values in synchronized cache:
'       1: broccoli
'       2: cauliflower
'       3: carrot
'       4: sorrel
'       5: baby turnip
'       6: beet
'       7: brussel sprout
'       8: cabbage
'       9: plantain
'       10: spinach
'       11: grape leaves
'       12: lime leaves
'       13: corn
'       14: radish
'       15: green bean
'       16: raddichio
'       17: lima beans