System.Threading.ReaderWriterLockSlim, klasa

Ten artykuł zawiera dodatkowe uwagi dotyczące dokumentacji referencyjnej dla tego interfejsu API.

Służy ReaderWriterLockSlim do ochrony zasobu, który jest odczytywany przez wiele wątków i zapisywany przez jeden wątek naraz. ReaderWriterLockSlim umożliwia korzystanie z wielu wątków w trybie odczytu, umożliwia jednemu wątkowi tryb zapisu z wyłączną własnością blokady i umożliwia korzystanie z jednego wątku, który ma dostęp do odczytu w trybie odczytu, z którego wątek może zostać uaktualniony do trybu zapisu bez konieczności wykluczania dostępu do odczytu do zasobu.

Uwaga

Domyślnie nowe wystąpienia programu ReaderWriterLockSlim są tworzone z flagą LockRecursionPolicy.NoRecursion i nie zezwalają na rekursję. Te domyślne zasady są zalecane dla wszystkich nowych programowania, ponieważ rekursja wprowadza niepotrzebne komplikacje i sprawia, że kod jest bardziej podatny na zakleszczenia. Aby uprościć migrację z istniejących projektów korzystających Monitor z programu lub ReaderWriterLock, możesz użyć LockRecursionPolicy.SupportsRecursion flagi , aby utworzyć wystąpienia ReaderWriterLockSlim , które zezwalają na rekursję.

Wątek może wprowadzać blokadę w trzech trybach: tryb odczytu, tryb zapisu i tryb odczytu z możliwością uaktualnienia. (W pozostałej części tego tematu "tryb odczytu z możliwością uaktualnienia" jest określany jako "tryb uaktualniania", a fraza "enter mode" jest używana w preferencjach do dłuższej frazy "enter x the lock in x mode".

Niezależnie od zasad rekursji tylko jeden wątek może być w trybie zapisu w dowolnym momencie. Gdy wątek jest w trybie zapisu, żaden inny wątek nie może wejść do blokady w dowolnym trybie. Tylko jeden wątek może być w trybie uaktualniania w dowolnym momencie. Dowolna liczba wątków może być w trybie odczytu i może istnieć jeden wątek w trybie uaktualniania, podczas gdy inne wątki są w trybie odczytu.

Ważne

Ten typ implementuje IDisposable interfejs. Po zakończeniu korzystania z typu należy usunąć go bezpośrednio lub pośrednio. Aby usunąć typ bezpośrednio, wywołaj metodę Disposetry/catch w bloku. Aby usunąć go pośrednio, należy użyć konstrukcji języka, takiej jak using (w języku C#) lub Using (w Visual Basic). Aby uzyskać więcej informacji, zobacz sekcję "Using an Object that Implements IDisposable" (Używanie obiektu implementujące interfejs IDisposable) w temacie interfejsu IDisposable .

ReaderWriterLockSlim ma zarządzaną koligację wątków; oznacza to, że każdy Thread obiekt musi wykonywać własne wywołania metody w celu wprowadzania i zamykania trybów blokady. Żaden wątek nie może zmienić trybu innego wątku.

Jeśli obiekt ReaderWriterLockSlim nie zezwala na rekursję, wątek, który próbuje wprowadzić blokadę, może zablokować z kilku powodów:

  • Wątek, który próbuje wprowadzić bloki trybu odczytu, jeśli istnieją wątki oczekujące na wejście w tryb zapisu lub jeśli w trybie zapisu istnieje jeden wątek.

    Uwaga

    Blokowanie nowych czytelników, gdy pisarze są w kolejce, to polityka sprawiedliwości blokady, która faworyzuje pisarzy. Obecna polityka sprawiedliwości równoważy sprawiedliwość czytelnikom i pisarzom, aby promować przepływność w najbardziej typowych scenariuszach. Przyszłe wersje platformy .NET mogą wprowadzać nowe zasady sprawiedliwości.

  • Wątek, który próbuje wprowadzić bloki trybu uaktualniania, jeśli istnieje już wątek w trybie uaktualniania, jeśli istnieją wątki oczekujące na wejście w tryb zapisu lub jeśli w trybie zapisu istnieje jeden wątek.

  • Wątek, który próbuje wprowadzić bloki trybu zapisu, jeśli istnieje wątek w dowolnym z trzech trybów.

Blokady uaktualniania i obniżania poziomu

Tryb uaktualniania jest przeznaczony dla przypadków, w których wątek zwykle odczytuje z chronionego zasobu, ale może być konieczne zapisanie go w przypadku spełnienia określonego warunku. Wątek ReaderWriterLockSlim , który został wprowadzony w trybie uaktualniania, ma dostęp do odczytu do chronionego zasobu i może uaktualnić tryb zapisu, wywołując EnterWriteLock metody lub TryEnterWriteLock . Ponieważ w trybie uaktualniania w danym momencie może istnieć tylko jeden wątek, uaktualnianie do trybu zapisu nie może zakleszczeć, gdy rekursja jest niedozwolona, co jest zasadami domyślnymi.

Ważne

Niezależnie od zasad rekursji, wątek, który początkowo wprowadził tryb odczytu, nie może uaktualnić do trybu uaktualniania lub trybu zapisu, ponieważ ten wzorzec tworzy silne prawdopodobieństwo zakleszczenia. Jeśli na przykład dwa wątki w trybie odczytu spróbują wejść w tryb zapisu, zakleszczą się. Tryb uaktualniania został zaprojektowany w celu uniknięcia takich zakleszczeń.

Jeśli istnieją inne wątki w trybie odczytu, wątek, który uaktualnia bloki. Gdy wątek jest zablokowany, inne wątki, które próbują wejść w tryb odczytu, są blokowane. Gdy wszystkie wątki wyszły z trybu odczytu, zablokowany wątek z możliwością uaktualnienia przechodzi w tryb zapisu. Jeśli istnieją inne wątki oczekujące na wejście w tryb zapisu, pozostają zablokowane, ponieważ pojedynczy wątek, który jest w trybie uaktualniania, uniemożliwia im uzyskanie wyłącznego dostępu do zasobu.

Gdy wątek w trybie uaktualniania kończy tryb zapisu, inne wątki oczekujące na wejście w tryb odczytu mogą to zrobić, chyba że istnieją wątki oczekujące na wejście w tryb zapisu. Wątek w trybie uaktualniania może uaktualnić i obniżyć na czas nieokreślony, o ile jest to jedyny wątek zapisywany w chronionym zasobie.

Ważne

Jeśli zezwolisz wielu wątkom na wprowadzanie trybu zapisu lub trybu uaktualniania, nie można zezwolić jednemu wątkowi na monopolizację trybu uaktualniania. W przeciwnym razie wątki, które próbują wejść w tryb zapisu bezpośrednio, będą blokowane na czas nieokreślony, a gdy są blokowane, inne wątki nie będą mogły wejść w tryb odczytu.

Wątek w trybie uaktualniania może obniżyć poziom do trybu odczytu, wywołując najpierw metodę EnterReadLock , a następnie wywołując metodę ExitUpgradeableReadLock . Ten wzorzec obniżania poziomu jest dozwolony dla wszystkich zasad rekursji blokady, nawet NoRecursion.

Po obniżeniu trybu odczytu wątek nie może ponownie przeprowadzić trybu uaktualniania, dopóki nie zostanie wyłączony z trybu odczytu.

Wprowadź blokadę rekursywnie

Można utworzyć obiekt ReaderWriterLockSlim obsługujący cykliczny wpis blokady przy użyciu ReaderWriterLockSlim(LockRecursionPolicy) konstruktora, który określa zasady blokowania i określa wartość LockRecursionPolicy.SupportsRecursion.

Uwaga

Stosowanie rekursji nie jest zalecane w przypadku nowego programowania, ponieważ wprowadza niepotrzebne komplikacje i sprawia, że kod jest bardziej podatny na zakleszczenia.

W przypadku elementu ReaderWriterLockSlim , który umożliwia rekursję, można powiedzieć o trybach, które może wprowadzić wątek:

  • Wątek w trybie odczytu może przechodzić w tryb odczytu rekursywnie, ale nie może wejść w tryb zapisu lub tryb uaktualniania. Jeśli spróbuje to zrobić, zostanie zgłoszony.LockRecursionException Wprowadzanie trybu odczytu, a następnie wprowadzanie trybu zapisu lub trybu uaktualniania jest wzorcem z silnym prawdopodobieństwem zakleszczeń, więc nie jest dozwolony. Jak wspomniano wcześniej, tryb uaktualniania jest udostępniany w przypadkach, w których konieczne jest uaktualnienie blokady.

  • Wątek w trybie uaktualniania może wprowadzać tryb zapisu i/lub tryb odczytu i może przechodzić dowolnego z trzech trybów cyklicznych. Jednak próba wprowadzenia bloków trybu zapisu, jeśli istnieją inne wątki w trybie odczytu.

  • Wątek w trybie zapisu może przechodzić w tryb odczytu i/lub tryb uaktualniania i może wejść w dowolny z trzech trybów cyklicznych.

  • Wątek, który nie wprowadził blokady, może wejść w dowolny tryb. Ta próba może zablokować z tych samych powodów, co próba wprowadzenia blokady niecyklicznej.

Wątek może zamknąć tryby wprowadzone w dowolnej kolejności, o ile wyjdą z każdego trybu dokładnie tyle razy, ile wszedł w ten tryb. Jeśli wątek próbuje zamknąć tryb zbyt wiele razy lub aby zamknąć tryb, który nie został wprowadzony, SynchronizationLockException jest zgłaszany.

Stany blokady

Może okazać się przydatne, aby myśleć o blokadzie pod względem jego stanów. Element ReaderWriterLockSlim może znajdować się w jednym z czterech stanów: nie został wprowadzony, odczyt, uaktualnienie i zapis.

  • Nie wprowadzono: w tym stanie żadne wątki nie zostały wprowadzone do blokady (lub wszystkie wątki zakończyły blokadę).

  • Przeczytaj: W tym stanie co najmniej jeden wątek wprowadził blokadę dostępu do odczytu do chronionego zasobu.

    Uwaga

    Wątek może wprowadzić blokadę w trybie odczytu przy użyciu EnterReadLock metod lub TryEnterReadLock lub przez obniżenie poziomu z trybu uaktualniania.

  • Uaktualnianie: w tym stanie jeden wątek wprowadził blokadę dostępu do odczytu z opcją uaktualnienia do dostępu do zapisu (czyli w trybie uaktualniania) i wprowadzono blokadę dostępu do odczytu. Nie więcej niż jeden wątek jednocześnie może wprowadzić blokadę z opcją uaktualnienia; dodatkowe wątki, które próbują wprowadzić tryb uaktualniania, są blokowane.

  • Zapis: w tym stanie jeden wątek wprowadził blokadę dostępu do zapisu do chronionego zasobu. Ten wątek ma wyłączne posiadanie blokady. Każdy inny wątek, który próbuje wprowadzić blokadę z jakiegokolwiek powodu, jest zablokowany.

W poniższej tabeli opisano przejścia między stanami blokad, dla blokad, które nie zezwalają na rekursję, gdy wątek t podejmuje akcję opisaną w kolumnie po lewej stronie. W momencie wykonywania akcji t nie ma trybu. (Specjalny przypadek, w którym t jest w trybie uaktualniania, jest opisany w przypisach dolnych tabeli). W górnym wierszu opisano stan początkowy blokady. Komórki opisują, co się dzieje z wątkiem, i pokazują zmiany stanu blokady w nawiasach.

Przejście Nie wprowadzono (N) Odczyt (R) Uaktualnianie (U) Zapis (W)
t wprowadza tryb odczytu t enters (R). t bloki, jeśli wątki oczekują na tryb zapisu; t w przeciwnym razie wprowadza. t bloki, jeśli wątki oczekują na tryb zapisu; t w przeciwnym razie wprowadza.1 t Bloki.
t wprowadza tryb uaktualniania t enters (U). t blokuje, jeśli wątki oczekują na tryb zapisu lub tryb uaktualniania; t w przeciwnym razie wprowadza wartość (U). t Bloki. t Bloki.
t wprowadza tryb zapisu t enters (W). t Bloki. t Bloki.2 t Bloki.

1 Jeśli t rozpoczyna się w trybie uaktualniania, wchodzi w tryb odczytu. Ta akcja nigdy nie blokuje. Stan blokady nie zmienia się. (Wątek może następnie wykonać obniżenie poziomu do trybu odczytu, zamykając tryb uaktualniania).

2 Jeśli t rozpoczyna się w trybie uaktualniania, blokuje, czy istnieją wątki w trybie odczytu. W przeciwnym razie uaktualnia się do trybu zapisu. Stan blokady zmienia się na Zapis (W). Jeśli t bloki, ponieważ istnieją wątki w trybie odczytu, wchodzi w tryb zapisu, gdy tylko ostatni wątek zakończy tryb odczytu, nawet jeśli wątek czeka na wejście w tryb zapisu.

Gdy wystąpi zmiana stanu, ponieważ wątek zamyka blokadę, następny wątek do przebudzenia jest wybierany w następujący sposób:

  • Po pierwsze, wątek, który czeka na tryb zapisu i jest już w trybie uaktualniania (może istnieć co najwyżej jeden taki wątek).
  • W przeciwnym razie wątek, który czeka na tryb zapisu.
  • W przeciwnym razie wątek, który czeka na tryb uaktualniania.
  • W przeciwnym razie wszystkie wątki oczekujące na tryb odczytu.

Kolejnym stanem blokady jest zawsze zapis (W) w dwóch pierwszych przypadkach i uaktualnienie (U) w trzecim przypadku, niezależnie od stanu blokady, gdy wątek wyjścia wyzwolił zmianę stanu. W ostatnim przypadku stan blokady to Uaktualnienie (U), jeśli istnieje wątek w trybie uaktualniania po zmianie stanu, a odczyt (R) w przeciwnym razie, niezależnie od poprzedniego stanu.

Przykłady

W poniższym przykładzie przedstawiono prostą zsynchronizowaną pamięć podręczną zawierającą ciągi z kluczami całkowitymi. Wystąpienie ReaderWriterLockSlim klasy służy do synchronizowania dostępu do Dictionary<TKey,TValue> obiektu, który służy jako wewnętrzna pamięć podręczna.

Przykład zawiera proste metody dodawania do pamięci podręcznej, usuwania z pamięci podręcznej i odczytywania z pamięci podręcznej. Aby zademonstrować przekroczenia limitu czasu, przykład zawiera metodę dodaną do pamięci podręcznej tylko wtedy, gdy może to zrobić w określonym przedziale czasu.

Aby zademonstrować tryb uaktualniania, przykład zawiera metodę, która pobiera wartość skojarzona z kluczem i porównuje ją z nową wartością. Jeśli wartość jest niezmieniona, metoda zwraca stan wskazujący brak zmian. Jeśli dla klucza nie zostanie znaleziona żadna wartość, zostanie wstawiona para klucz/wartość. Jeśli wartość została zmieniona, zostanie ona zaktualizowana. Tryb uaktualniania umożliwia wątkowi uaktualnienie z dostępu do odczytu do zapisu zgodnie z potrzebami bez ryzyka zakleszczenia.

Przykład zawiera zagnieżdżone wyliczenie, które określa zwracane wartości dla metody, która demonstruje tryb uaktualniania.

W przykładzie użyto konstruktora bez parametrów do utworzenia blokady, więc rekursja nie jest dozwolona. Programowanie jest ReaderWriterLockSlim prostsze i mniej podatne na błędy, gdy blokada nie zezwala na rekursję.

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

Poniższy kod używa SynchronizedCache następnie obiektu do przechowywania słownika nazw warzyw. Tworzy trzy zadania. Pierwszy zapisuje nazwy warzyw przechowywanych w tablicy do SynchronizedCache wystąpienia. Drugie i trzecie zadanie wyświetla nazwy warzyw, pierwszy w kolejności rosnącej (od niskiego indeksu do wysokiego indeksu), drugi w kolejności malejącej. Ostatnie zadanie wyszukuje ciąg "ogórk" i, gdy go znajdzie, wywołuje EnterUpgradeableReadLock metodę, aby zastąpić ciąg "zielony fasola".

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