Freigeben über


System.Threading.ReaderWriterLockSlim-Klasse

Dieser Artikel enthält ergänzende Hinweise zur Referenzdokumentation für diese API.

Dient ReaderWriterLockSlim zum Schutz einer Ressource, die von mehreren Threads gelesen und jeweils von einem Thread in einen Thread geschrieben wird. ReaderWriterLockSlim ermöglicht es mehreren Threads, sich im Lesemodus zu befinden, ein Thread im Schreibmodus mit exklusivem Besitz der Sperre zu sein und einen Thread mit Lesezugriff im Upgrademodus zu verwenden, aus dem der Thread auf den Schreibmodus aktualisieren kann, ohne den Lesezugriff auf die Ressource zurückzugeben.

Hinweis

Standardmäßig werden neue Instanzen mit ReaderWriterLockSlim dem LockRecursionPolicy.NoRecursion Flag erstellt und lassen keine Rekursion zu. Diese Standardrichtlinie wird für alle neuen Entwicklungen empfohlen, da rekursion unnötige Komplikationen einführt und Ihren Code anfälliger für Deadlocks macht. Um die Migration von vorhandenen Projekten zu vereinfachen, die verwendet Monitor werden, oder ReaderWriterLockverwenden Sie das LockRecursionPolicy.SupportsRecursion Flag zum Erstellen von Instanzen, die ReaderWriterLockSlim rekursion zulassen.

Ein Thread kann die Sperre in drei Modi eingeben: Lesemodus, Schreibmodus und upgradefähiger Lesemodus. (Im restlichen Thema wird "Upgradeable Read Mode" als "upgradeabler Modus" bezeichnet, und der Ausdruck "Eingabemodus x " wird vor dem längeren Ausdruck "In den Sperrmodus x eingeben" verwendet.)

Unabhängig von der Rekursionsrichtlinie kann immer nur ein Thread im Schreibmodus ausgeführt werden. Wenn sich ein Thread im Schreibmodus befindet, kann kein anderer Thread die Sperre in einen beliebigen Modus eingeben. Es kann jeweils nur ein Thread im Upgrademodus ausgeführt werden. Eine beliebige Anzahl von Threads kann sich im Lesemodus befinden, und es kann einen Thread im Upgrademodus geben, während sich andere Threads im Lesemodus befinden.

Wichtig

Dieser Typ implementiert die IDisposable-Schnittstelle. Nach Abschluss der Verwendung sollten Sie den Typ entweder direkt oder indirekt löschen. Zum direkten Löschen des Typs rufen Sie seine Dispose-Methode in einem try/catch-Block auf. Zum indirekten Löschen verwenden Sie ein Sprachkonstrukt wie using (in C#) oder Using (in Visual Basic). Weitere Informationen finden Sie im Abschnitt „Verwenden eines Objekts, das IDisposable implementiert“ des Themas „Die IDisposable-Schnittstelle“.

ReaderWriterLockSlim hat die Threadaffinität verwaltet; d. h., jedes Thread Objekt muss eigene Methodenaufrufe ausführen, um den Sperrmodus ein- und auszuschalten. Kein Thread kann den Modus eines anderen Threads ändern.

Wenn eine ReaderWriterLockSlim Rekursion nicht zulässt, kann ein Thread, der versucht, die Sperre einzugeben, aus mehreren Gründen blockieren:

  • Ein Thread, der versucht, in den Lesemodus zu wechseln, wenn Threads vorhanden sind, die auf den Schreibmodus warten oder ein einzelner Thread im Schreibmodus vorhanden ist.

    Hinweis

    Das Blockieren neuer Leser, wenn Autoren in die Warteschlange gestellt werden, ist eine Sperr-Fairness-Richtlinie, die Autoren bevorzugt. Die aktuelle Fairnesspolitik gleicht Fairness mit Lesern und Autoren ab, um den Durchsatz in den am häufigsten verwendeten Szenarien zu fördern. Zukünftige Versionen von .NET können neue Fairnessrichtlinien einführen.

  • Ein Thread, der versucht, upgradefähige Modus zu öffnen, blockiert, wenn bereits ein Thread im upgradefähigen Modus vorhanden ist, wenn Threads warten, um in den Schreibmodus zu wechseln, oder wenn ein einzelner Thread im Schreibmodus vorhanden ist.

  • Ein Thread, der versucht, Schreibmodusblöcke einzugeben, wenn ein Thread in einem der drei Modi vorhanden ist.

Upgrade- und Downgradesperren

Der upgradefähige Modus ist für Fälle gedacht, in denen ein Thread in der Regel aus der geschützten Ressource liest, aber ggf. schreiben muss, wenn eine Bedingung erfüllt ist. Ein Thread, der in einen ReaderWriterLockSlim upgradefähigen Modus eingegeben hat, hat Lesezugriff auf die geschützte Ressource und kann durch Aufrufen der Methoden TryEnterWriteLock auf den EnterWriteLock Schreibmodus aktualisiert werden. Da es jeweils nur einen Thread im upgradefähigen Modus geben kann, kann das Upgrade auf den Schreibmodus nicht inaktiv sein, wenn rekursion nicht zulässig ist, was die Standardrichtlinie ist.

Wichtig

Unabhängig von der Rekursionsrichtlinie ist ein Thread, der zunächst in den Lesemodus wechselt, nicht in den upgradefähigen Modus oder schreibmodus zu aktualisieren, da dieses Muster eine starke Wahrscheinlichkeit von Deadlocks erzeugt. Wenn z. B. zwei Threads im Lesemodus beide versuchen, in den Schreibmodus zu wechseln, werden sie inaktiv. Der upgradefähige Modus wurde entwickelt, um solche Deadlocks zu vermeiden.

Wenn andere Threads im Lesemodus vorhanden sind, wird der Thread, der Blöcke aktualisiert, ausgeführt. Während der Thread blockiert ist, werden andere Threads, die versuchen, in den Lesemodus zu wechseln, blockiert. Wenn alle Threads aus dem Lesemodus beendet wurden, wechselt der blockierte upgradefähige Thread in den Schreibmodus. Wenn andere Threads auf den Schreibmodus warten, werden sie erneut blockiert Standard, da der einzelne Thread, der sich im upgradefähigen Modus befindet, verhindert, dass sie exklusiven Zugriff auf die Ressource erhalten.

Wenn der Thread im upgradefähigen Modus den Schreibmodus verlässt, können andere Threads, die auf den Lesemodus warten, dies tun, es sei denn, es gibt Threads, die auf den Schreibmodus warten. Der Thread im upgradefähigen Modus kann unbegrenzt aktualisiert und heruntergestuft werden, solange es sich um den einzigen Thread handelt, der in die geschützte Ressource schreibt.

Wichtig

Wenn Sie zulassen, dass mehrere Threads in den Schreibmodus oder in den upgradefähigen Modus wechseln können, dürfen Sie nicht zulassen, dass ein Thread den upgradefähigen Modus monopolisiert. Andernfalls werden Threads, die versuchen, den Schreibmodus direkt einzugeben, unbegrenzt blockiert, und während sie blockiert werden, können andere Threads nicht in den Lesemodus wechseln.

Ein Thread im upgradefähigen Modus kann einen Downgrade in den Lesemodus durchführen, indem zuerst die EnterReadLock Methode aufgerufen und dann die ExitUpgradeableReadLock Methode aufgerufen wird. Dieses Downgrademuster ist für alle Sperr-Rekursionsrichtlinien zulässig, auch NoRecursion.

Nach dem Herunterstufen in den Lesemodus kann ein Thread den upgradefähigen Modus erst wieder ausführen, wenn er aus dem Lesemodus beendet wurde.

Rekursives Eingeben der Sperre

Sie können eine ReaderWriterLockSlim , die rekursive Sperreingabe unterstützt, mithilfe des Konstruktors erstellen, der ReaderWriterLockSlim(LockRecursionPolicy) die Sperrrichtlinie angibt, und angeben LockRecursionPolicy.SupportsRecursion.

Hinweis

Die Verwendung von Rekursionen wird für die neue Entwicklung nicht empfohlen, da sie unnötige Komplikationen einführt und Ihren Code anfälliger für Deadlocks macht.

Für ein ReaderWriterLockSlim Element, das rekursion zulässt, kann Folgendes über die Modi gesagt werden, die ein Thread eingeben kann:

  • Ein Thread im Lesemodus kann rekursiv in den Lesemodus wechseln, aber nicht in den Schreibmodus oder in den Upgrademodus wechseln. Wenn dies versucht wird, wird ein LockRecursionException Fehler ausgelöst. Wenn Sie in den Lesemodus wechseln und dann in den Schreib- oder Upgrademodus wechseln, handelt es sich um ein Muster mit einer starken Wahrscheinlichkeit von Deadlocks, sodass sie nicht zulässig ist. Wie bereits erwähnt, wird der upgradefähige Modus für Fälle bereitgestellt, in denen ein Upgrade einer Sperre erforderlich ist.

  • Ein Thread im upgradefähigen Modus kann in den Schreib- und/oder Lesemodus wechseln und einen der drei Modi rekursiv eingeben. Es wird jedoch versucht, Schreibmodusblöcke einzugeben, wenn andere Threads im Lesemodus vorhanden sind.

  • Ein Thread im Schreibmodus kann in den Lese- und/oder Upgrademodus wechseln und einen der drei Modi rekursiv eingeben.

  • Ein Thread, der die Sperre nicht eingegeben hat, kann in einen beliebigen Modus wechseln. Dieser Versuch kann aus denselben Gründen wie ein Versuch, eine nicht rekursive Sperre einzugeben, blockieren.

Ein Thread kann die modi beenden, die er in beliebiger Reihenfolge eingegeben hat, solange er jeden Modus genau so oft beendet, wie er in diesen Modus gelangt ist. Wenn ein Thread versucht, einen Modus zu oft zu beenden oder einen nicht eingegebenen Modus zu beenden, wird ein SynchronizationLockException Fehler ausgelöst.

Sperrstatus

Sie können es hilfreich finden, um die Sperre in Bezug auf ihre Zustände zu betrachten. Eine ReaderWriterLockSlim Kann in einem von vier Zuständen sein: nicht eingegeben, gelesen, aktualisiert und geschrieben.

  • Nicht eingegeben: In diesem Zustand haben keine Threads die Sperre eingegeben (oder alle Threads haben die Sperre beendet).

  • Lesen: In diesem Zustand haben mindestens ein Threads die Sperre für den Lesezugriff auf die geschützte Ressource eingegeben.

    Hinweis

    Ein Thread kann die Sperre im Lesemodus mithilfe der EnterReadLock Methoden oder TryEnterReadLock durch Herabstufen aus dem upgradefähigen Modus eingeben.

  • Upgrade: In diesem Zustand hat ein Thread die Sperre für den Lesezugriff mit der Option zum Upgrade auf Schreibzugriff (d. h. im upgradefähigen Modus) eingegeben, und null oder mehr Threads haben die Sperre für den Lesezugriff eingegeben. Nicht mehr als ein Thread gleichzeitig kann die Sperre mit der Option zum Upgrade eingeben. Zusätzliche Threads, die versuchen, in den Upgrademodus zu wechseln, werden blockiert.

  • Schreiben: In diesem Zustand hat ein Thread die Sperre für den Schreibzugriff auf die geschützte Ressource eingegeben. Dieser Thread verfügt über exklusiven Besitz der Sperre. Jeder andere Thread, der versucht, die Sperre aus irgendeinem Grund einzugeben, wird blockiert.

In der folgenden Tabelle werden die Übergänge zwischen Sperreszuständen für Sperren beschrieben, die keine Rekursion zulassen, wenn ein Thread t die in der spalte ganz links beschriebene Aktion ausführt. Zu dem Zeitpunkt, zu dem die Aktion ausgeführt wird, t ist kein Modus vorhanden. (Der Sonderfall, in dem t sich der Upgrademodus befindet, wird in den Tabellennoten beschrieben.) In der obersten Zeile wird der Startzustand der Sperre beschrieben. In den Zellen wird beschrieben, was mit dem Thread geschieht, und Änderungen am Sperrzustand in Klammern angezeigt.

Übergang Nicht eingegeben (N) Lesen (Read, R) Upgrade (U) Schreiben (Write, W)
t Wechselt in den Lesemodus t enters (R). t blockiert, wenn Threads auf den Schreibmodus warten; t andernfalls wird die Zeichenfolge eingegeben. t blockiert, wenn Threads auf den Schreibmodus warten; t andernfalls wird die Zeichenfolge eingegeben.1 t Blöcke.
t Wechselt in den upgradefähigen Modus t gibt (U) ein. t blockiert, wenn Threads auf den Schreibmodus oder den Upgrademodus warten; t andernfalls wird (U) eingegeben. t Blöcke. t Blöcke.
t Wechselt in den Schreibmodus t enters (W). t Blöcke. t Blöcke.2 t Blöcke.

1 Wenn t sie im upgradefähigen Modus gestartet wird, wechselt er in den Lesemodus. Diese Aktion blockiert niemals. Der Sperrzustand ändert sich nicht. (Der Thread kann dann ein Downgrade zum Lesemodus abschließen, indem er den upgradefähigen Modus beendet.)

2 Wenn t sie im upgradefähigen Modus gestartet werden, wird blockiert, ob Threads im Lesemodus vorhanden sind. Andernfalls wird der Schreibmodus aktualisiert. Der Sperrzustand wird in Write (W) geändert. Wenn t Blöcke vorhanden sind, da Threads im Lesemodus vorhanden sind, wechselt er in den Schreibmodus, sobald der letzte Thread den Lesemodus beendet, auch wenn Threads warten, um in den Schreibmodus zu wechseln.

Wenn eine Zustandsänderung auftritt, weil ein Thread die Sperre verlässt, wird der nächste zu weckende Thread wie folgt ausgewählt:

  • Zuerst wartet ein Thread auf den Schreibmodus und befindet sich bereits im Upgrademodus (es kann höchstens einen solchen Thread geben).
  • Andernfalls wartet ein Thread, der auf den Schreibmodus wartet.
  • Andernfalls wartet ein Thread, der auf den upgradefähigen Modus wartet.
  • Andernfalls warten alle Threads, die auf den Lesemodus warten.

Der nachfolgende Zustand der Sperre ist immer Write (W) in den ersten beiden Fällen und Upgrade (U) im dritten Fall, unabhängig vom Zustand der Sperre, wenn der beendigungsthread die Zustandsänderung ausgelöst hat. Im letzten Fall ist der Zustand der Sperre "Upgrade (U)", wenn ein Thread nach der Zustandsänderung im Upgrademodus vorhanden ist, andernfalls "Read (R)", unabhängig vom vorherigen Zustand.

Beispiele

Das folgende Beispiel zeigt einen einfachen synchronisierten Cache, der Zeichenfolgen mit ganzzahligen Schlüsseln enthält. Eine Instanz von ReaderWriterLockSlim wird verwendet, um den Zugriff auf den Dictionary<TKey,TValue> internen Cache zu synchronisieren.

Das Beispiel enthält einfache Methoden zum Hinzufügen zum Cache, Löschen aus dem Cache und Lesen aus dem Cache. Zum Veranschaulichen von Timeouts enthält das Beispiel eine Methode, die dem Cache nur hinzugefügt wird, wenn dies innerhalb eines angegebenen Timeouts möglich ist.

Um den upgradefähigen Modus zu veranschaulichen, enthält das Beispiel eine Methode, die den einem Schlüssel zugeordneten Wert abruft und ihn mit einem neuen Wert vergleicht. Wenn der Wert unverändert ist, gibt die Methode einen Status zurück, der keine Änderung angibt. Wenn kein Wert für den Schlüssel gefunden wird, wird das Schlüssel-Wert-Paar eingefügt. Wenn sich der Wert geändert hat, wird er aktualisiert. Der upgradefähige Modus ermöglicht es dem Thread, von Lesezugriff auf Schreibzugriff nach Bedarf zu aktualisieren, ohne dass Das Risiko von Deadlocks besteht.

Das Beispiel enthält eine geschachtelte Enumeration, die die Rückgabewerte für die Methode angibt, die den upgradefähigen Modus veranschaulicht.

Im Beispiel wird der parameterlose Konstruktor zum Erstellen der Sperre verwendet, sodass rekursion nicht zulässig ist. Die ReaderWriterLockSlim Programmierung ist einfacher und weniger anfällig für Fehler, wenn die Sperre keine Rekursion zulässt.

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

Der folgende Code verwendet dann das SynchronizedCache Objekt, um ein Wörterbuch mit Gemüsenamen zu speichern. Es werden drei Aufgaben erstellt. Im ersten Wird die Namen von Gemüse geschrieben, die in einem Array gespeichert sind, in eine SynchronizedCache Instanz. Der zweite und dritte Vorgang zeigt die Namen des Gemüses an, die erste in aufsteigender Reihenfolge (von niedriger Index bis hoch index), die zweite in absteigender Reihenfolge. Der letzte Vorgang sucht nach der Zeichenfolge "Gurke" und ruft die Methode auf, um die EnterUpgradeableReadLock Zeichenfolge "grüne Bohnen" zu ersetzen.

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