System.Threading.ReaderWriterLockSlim – třída

Tento článek obsahuje doplňující poznámky k referenční dokumentaci pro toto rozhraní API.

Slouží ReaderWriterLockSlim k ochraně prostředku, který je přečten více vlákny a zapsán do jednoho vlákna najednou. ReaderWriterLockSlim umožňuje, aby více vláken bylo v režimu čtení, umožňuje, aby jedno vlákno bylo v režimu zápisu s výhradním vlastnictvím zámku, a umožňuje, aby jedno vlákno, které má přístup ke čtení, bylo v upgradovatelném režimu čtení, ze kterého může vlákno upgradovat na režim zápisu, aniž by muselo relinquishovat svůj přístup ke čtení k prostředku.

Poznámka:

Ve výchozím nastavení se vytvoří nové instance ReaderWriterLockSlim s příznakem LockRecursionPolicy.NoRecursion a neumožňují rekurze. Tato výchozí zásada se doporučuje pro veškerý nový vývoj, protože rekurze představuje zbytečné komplikace a váš kód je náchylnější k zablokování. Pokud chcete zjednodušit migraci z existujících projektů, které používají Monitor , ReaderWriterLocknebo můžete pomocí LockRecursionPolicy.SupportsRecursion příznaku ReaderWriterLockSlim vytvořit instance, které umožňují rekurze.

Vlákno může vstoupit do zámku ve třech režimech: režim čtení, režim zápisu a upgradovatelný režim čtení. (Ve zbytku tohoto tématu se "upgradovatelný režim čtení" označuje jako "upgradeable mode" (režim upgradu) a fráze "Enter x mode" se používá v předvolbě pro delší frázi "enter the lock in x mode".)

Bez ohledu na zásady rekurze může být vždy v režimu zápisu pouze jedno vlákno. Pokud je vlákno v režimu zápisu, žádné jiné vlákno nemůže vstoupit do zámku v jakémkoli režimu. V režimu upgradu může být kdykoli pouze jedno vlákno. V režimu čtení může být libovolný počet vláken a v režimu upgradu může existovat jedno vlákno, zatímco ostatní vlákna jsou v režimu čtení.

Důležité

Tento typ implementuje IDisposable rozhraní. Jakmile typ dokončíte, měli byste ho odstranit buď přímo, nebo nepřímo. Chcete-li odstranit typ přímo, zavolejte jeho Dispose metodu try/catch v bloku. Pokud ho chcete zlikvidovat nepřímo, použijte konstruktor jazyka, například using (v jazyce C#) nebo Using (v jazyce Visual Basic). Další informace naleznete v části Použití objektu, který implementuje IDisposable v IDisposable tématu rozhraní.

ReaderWriterLockSlim má spravované spřažení vláken; to znamená, že každý Thread objekt musí provádět vlastní volání metody pro vstup a ukončení režimů uzamčení. Žádné vlákno nemůže změnit režim jiného vlákna.

ReaderWriterLockSlim Pokud rekurze nepovolí, může vlákno, které se pokusí zadat zámek, blokovat z několika důvodů:

  • Vlákno, které se pokusí vstoupit do bloků režimu čtení, pokud existují vlákna čekající na vstup do režimu zápisu nebo pokud existuje jedno vlákno v režimu zápisu.

    Poznámka:

    Blokování nových čtenářů, když se zapisovače zařadí do fronty, je zásady spravedlnosti zámků, které upřednostňují spisovatele. Aktuální zásada spravedlnosti vyrovnává nestrannost čtenářům a zapisovačům, aby se podpořila propustnost v nejběžnějších scénářích. Budoucí verze rozhraní .NET mohou zavést nové zásady nestrannosti.

  • Vlákno, které se pokusí vstoupit do upgradovatelného režimu bloky, pokud již existuje vlákno v režimu upgradu, pokud existují vlákna čekající na vstup do režimu zápisu nebo pokud existuje jedno vlákno v režimu zápisu.

  • Vlákno, které se pokusí vstoupit do bloků režimu zápisu, pokud je vlákno v některém ze tří režimů.

Upgrade a downgrade zámků

Režim upgradu je určený pro případy, kdy vlákno obvykle čte z chráněného prostředku, ale může do něj muset zapisovat, pokud je splněna určitá podmínka. Vlákno, které vstoupilo do upgradovatelného režimu, má přístup pro čtení k chráněnému ReaderWriterLockSlim prostředku a může upgradovat na režim zápisu EnterWriteLock voláním nebo TryEnterWriteLock metodami. Vzhledem k tomu, že v režimu upgradu může najednou existovat pouze jedno vlákno, nemůže upgrade na režim zápisu zablokovat, pokud rekurze není povolena, což je výchozí zásada.

Důležité

Bez ohledu na zásady rekurze není vlákno, které původně vstoupilo do režimu čtení, povoleno upgradovat na upgradovatelný režim nebo režim zápisu, protože tento model vytváří silnou pravděpodobnost zablokování. Pokud se například dvě vlákna v režimu čtení pokusí vstoupit do režimu zápisu, zablokují se. Upgradovatelný režim je navržený tak, aby se takovým zablokováním vyhnul.

Pokud jsou v režimu čtení další vlákna, vlákno, které upgraduje bloky. Zatímco je vlákno blokováno, ostatní vlákna, která se pokusí přejít do režimu čtení, jsou blokována. Pokud všechna vlákna ukončila režim čtení, blokované upgradovatelné vlákno přejde do režimu zápisu. Pokud existují další vlákna čekající na vstup do režimu zápisu, zůstanou blokované, protože jedno vlákno, které je v režimu upgradu, jim brání v získání výhradního přístupu k prostředku.

Když vlákno v režimu upgradu ukončí režim zápisu, mohou to provést další vlákna čekající na vstup do režimu čtení, pokud neexistují vlákna čekající na vstup do režimu zápisu. Vlákno v upgradovatelném režimu může upgradovat a downgradovat neomezeně dlouho, pokud se jedná o jediné vlákno, které zapisuje do chráněného prostředku.

Důležité

Pokud povolíte, aby více vláken vstoupilo do režimu zápisu nebo upgradovatelný režim, nesmíte povolit jedno vlákno monopolizovat upgradovatelný režim. V opačném případě budou vlákna, která se pokusí přejít do režimu zápisu přímo, blokována na neomezenou dobu a zatímco jsou blokovaná, ostatní vlákna nebudou moci vstoupit do režimu čtení.

Vlákno v upgradovatelném režimu může downgradovat na režim čtení tím, že nejprve zavolá metodu EnterReadLock a pak zavolá metodu ExitUpgradeableReadLock . Tento model downgradu je povolený pro všechny zásady rekurze zámků, a to i NoRecursion.

Po downgradu do režimu čtení nemůže vlákno znovu upgradovat režim, dokud se neukončí z režimu čtení.

Rekurzivně zadejte zámek.

Pomocí ReaderWriterLockSlim konstruktoru ReaderWriterLockSlim(LockRecursionPolicy) , který určuje zásady uzamčení, a zadat LockRecursionPolicy.SupportsRecursion.

Poznámka:

Použití rekurze se nedoporučuje pro nový vývoj, protože představuje zbytečné komplikace a je náchylnější k zablokování kódu.

ReaderWriterLockSlim Pro to, který umožňuje rekurzi, lze o režimech, které vlákno může zadat:

  • Vlákno v režimu čtení může rekurzivně vstoupit do režimu čtení, ale nemůže vstoupit do režimu zápisu nebo upgradovatelný režim. Pokud se to pokusí, je LockRecursionException vyvolán. Vstup do režimu čtení a následný přechod do režimu zápisu nebo upgrade je vzor se silnou pravděpodobností zablokování, takže není povolený. Jak je popsáno dříve, je k dispozici režim upgradu pro případy, kdy je nutné upgradovat zámek.

  • Vlákno v upgradovatelném režimu může vstoupit do režimu zápisu nebo režimu čtení a může rekurzivně vstoupit do libovolného ze tří režimů. Pokus o vstup do bloků režimu zápisu však v případě, že existují jiná vlákna v režimu čtení.

  • Vlákno v režimu zápisu může vstoupit do režimu čtení a/nebo upgradovatelný režim a může vstoupit do libovolného ze tří režimů rekurzivně.

  • Vlákno, které nezadali zámek, může vstoupit do jakéhokoli režimu. Tento pokus může blokovat stejné důvody jako pokus o zadání nerekurzivního zámku.

Vlákno může ukončit režimy, které zadal v libovolném pořadí, pokud ukončí každý režim přesně tolikrát, kolikrát vstoupil do daného režimu. Pokud se vlákno pokusí ukončit režim příliš mnohokrát nebo ukončit režim, který nebyl zadán, SynchronizationLockException vyvolá se.

Stavy uzamčení

Může být užitečné si zámek představit z hlediska jeho stavů. Může ReaderWriterLockSlim být v jednom ze čtyř stavů: nezadáno, číst, upgradovat a zapisovat.

  • Nezadáno: V tomto stavu nebyly zadaná žádná vlákna (nebo všechna vlákna zámek ukončila).

  • Čtení: V tomto stavu jedno nebo více vláken zadalo zámek pro přístup pro čtení k chráněnému prostředku.

    Poznámka:

    Vlákno může vstoupit do zámku v režimu čtení pomocí EnterReadLock metod, TryEnterReadLock nebo downgradem z upgradovatelného režimu.

  • Upgrade: V tomto stavu jedno vlákno zadalo zámek pro přístup pro čtení s možností upgradu na přístup k zápisu (tj. v upgradovatelném režimu) a nula nebo více vláken zadalo zámek pro přístup pro čtení. Zamykací zámek s možností upgradu nesmí najednou zadávat více než jedno vlákno; další vlákna, která se pokusí přejít do režimu upgradu, jsou blokovaná.

  • Zápis: V tomto stavu jedno vlákno zadalo zámek pro přístup k zápisu k chráněnému prostředku. Toto vlákno má výhradní vlastnictví zámku. Jakékoli jiné vlákno, které se pokusí zadat zámek z jakéhokoli důvodu, je blokováno.

Následující tabulka popisuje přechody mezi stavy zámků, pro zámky, které neumožňují rekurze, když vlákno t provede akci popsanou ve sloupci úplně vlevo. V době provedení akce t nemá žádný režim. (Zvláštní případ, kdy t je v režimu upgradu, je popsán v tabulce poznámek pod čarou.) Horní řádek popisuje počáteční stav zámku. Buňky popisují, co se stane s vláknem, a zobrazují změny stavu zámku v závorkách.

Transition Nezadá se (N) Číst (R) Upgrade (U) Zapisovat (W)
t přejde do režimu čtení. t zadá (R). t blokuje, pokud vlákna čekají na režim zápisu; t v opačném případě zadáte. t blokuje, pokud vlákna čekají na režim zápisu; t v opačném případě zadáte.1 t Bloky.
t přejde do režimu upgradu. t zadá (U). t blokuje, pokud vlákna čekají na režim zápisu nebo režim upgradu; t v opačném případě zadá (U). t Bloky. t Bloky.
t přejde do režimu zápisu. t zadá (W). t Bloky. t Bloky.2 t Bloky.

1 Pokud t se spustí v režimu upgradu, přejde do režimu čtení. Tato akce nikdy nezablokuje. Stav zámku se nezmění. (Vlákno pak může dokončit downgrade na režim čtení ukončením upgradovatelného režimu.)

2 Pokud t se spustí v režimu upgradu, blokuje, pokud existují vlákna v režimu čtení. Jinak se upgraduje na režim zápisu. Stav zámku se změní na Zápis (W). Pokud t bloky, protože existují vlákna v režimu čtení, přejde do režimu zápisu, jakmile poslední vlákno ukončí režim čtení, a to i v případě, že vlákna čekají na vstup do režimu zápisu.

Když dojde ke změně stavu, protože vlákno ukončí zámek, je vybráno následující vlákno, které se má probudit:

  • Nejprve vlákno, které čeká na režim zápisu a je již v režimu upgradu (může existovat maximálně jedno takové vlákno).
  • Pokud se to nepodaří, vlákno, které čeká na režim zápisu.
  • Pokud se to nepodaří, vlákno, které čeká na upgradovatelný režim.
  • Pokud se to nepodaří, všechna vlákna čekající na režim čtení.

Následující stav zámku je vždy zápis (W) v prvních dvou případech a Upgrade (U) ve třetím případě bez ohledu na stav zámku, když výstupní vlákno aktivovalo změnu stavu. V posledním případě je stav zámku Upgrade (U), pokud po změně stavu existuje vlákno v režimu upgradu a v opačném případě bez ohledu na předchozí stav.

Příklady

Následující příklad ukazuje jednoduchou synchronizovanou mezipaměť, která obsahuje řetězce s celočíselné klíče. Instance ReaderWriterLockSlim slouží k synchronizaci přístupu k Dictionary<TKey,TValue> té, která slouží jako vnitřní mezipaměť.

Příklad obsahuje jednoduché metody pro přidání do mezipaměti, odstranění z mezipaměti a čtení z mezipaměti. Aby bylo možné předvést vypršení časového limitu, obsahuje příklad metodu, která se přidá do mezipaměti pouze v případě, že to může provést v zadaném časovém limitu.

Pro předvedení upgradovatelného režimu obsahuje příklad metodu, která načte hodnotu přidruženou ke klíči a porovná ji s novou hodnotou. Pokud je hodnota beze změny, vrátí metoda stav označující žádnou změnu. Pokud se pro klíč nenajde žádná hodnota, vloží se dvojice klíč/hodnota. Pokud se hodnota změnila, aktualizuje se. Upgradovatelný režim umožňuje, aby vlákno podle potřeby upgraduje z přístupu pro čtení na přístup k zápisu bez rizika zablokování.

Příklad obsahuje vnořený výčet, který určuje návratové hodnoty pro metodu, která ukazuje režim upgradu.

Příklad používá konstruktor bez parametrů k vytvoření zámku, takže rekurze není povolena. ReaderWriterLockSlim Programování je jednodušší a méně náchylné k chybě, pokud zámek neumožňuje rekurzi.

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

Následující kód pak použije SynchronizedCache objekt k uložení slovníku názvů zeleniny. Vytvoří tři úkoly. První zapíše názvy zeleniny uložené v poli na SynchronizedCache instanci. Druhý a třetí úkol zobrazuje názvy zeleniny, první ve vzestupném pořadí (od nízkého indexu po vysoký index), druhý v sestupném pořadí. Konečný úkol vyhledá řetězec "ochutná" a když ho najde, zavolá EnterUpgradeableReadLock metodu, která nahradí řetězec "zelená bean".

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