Bagikan melalui


Kelas System.Threading.ReaderWriterLockSlim

Artikel ini menyediakan keterangan tambahan untuk dokumentasi referensi untuk API ini.

Gunakan ReaderWriterLockSlim untuk melindungi sumber daya yang dibaca oleh beberapa utas dan ditulis oleh satu utas sekaligus. ReaderWriterLockSlim memungkinkan beberapa utas berada dalam mode baca, memungkinkan satu utas berada dalam mode tulis dengan kepemilikan eksklusif kunci, dan memungkinkan satu utas yang memiliki akses baca berada dalam mode baca yang dapat ditingkatkan, dari mana utas dapat ditingkatkan ke mode tulis tanpa harus melepaskan akses bacanya ke sumber daya.

Catatan

Secara default, instans ReaderWriterLockSlim baru dibuat dengan LockRecursionPolicy.NoRecursion bendera dan tidak mengizinkan rekursi. Kebijakan default ini direkomendasikan untuk semua pengembangan baru, karena rekursi memperkenalkan komplikasi yang tidak perlu dan membuat kode Anda lebih rentan terhadap kebuntuan. Untuk menyederhanakan migrasi dari proyek yang ada yang menggunakan Monitor atau ReaderWriterLock, Anda dapat menggunakan LockRecursionPolicy.SupportsRecursion bendera untuk membuat instans ReaderWriterLockSlim yang memungkinkan rekursi.

Utas dapat memasuki kunci dalam tiga mode: mode baca, mode tulis, dan mode baca yang dapat ditingkatkan. (Dalam topik lainnya, "mode baca yang dapat ditingkatkan" disebut sebagai "mode yang dapat ditingkatkan", dan frasa "masukkan x mode" digunakan dalam preferensi frasa yang lebih panjang "masukkan kunci dalam x mode".)

Terlepas dari kebijakan rekursi, hanya satu utas yang dapat berada dalam mode tulis kapan saja. Saat utas dalam mode tulis, tidak ada utas lain yang dapat memasuki kunci dalam mode apa pun. Hanya satu utas yang dapat ditingkatkan kapan saja. Sejumlah utas dapat berada dalam mode baca, dan mungkin ada satu utas dalam mode yang dapat ditingkatkan sementara utas lain berada dalam mode baca.

Penting

Jenis ini mengimplementasikan IDisposable antarmuka. Setelah selesai menggunakan jenisnya, Anda harus membuangnya baik secara langsung maupun tidak langsung. Untuk membuang jenis secara langsung, panggil metodenya Dispose dalam try/catch blok. Untuk membuangnya secara tidak langsung, gunakan konstruksi bahasa seperti using (di C#) atau Using (di Visual Basic). Untuk informasi selengkapnya, lihat bagian "Menggunakan Objek yang Menerapkan IDisposable" dalam IDisposable topik antarmuka.

ReaderWriterLockSlim memiliki afinitas utas terkelola; artinya, setiap Thread objek harus melakukan panggilan metodenya sendiri untuk memasuki dan keluar dari mode kunci. Tidak ada utas yang dapat mengubah mode utas lain.

ReaderWriterLockSlim Jika tidak mengizinkan rekursi, utas yang mencoba memasukkan kunci dapat memblokir karena beberapa alasan:

  • Utas yang mencoba memasukkan blok mode baca jika ada utas yang menunggu untuk memasuki mode tulis atau jika ada satu utas dalam mode tulis.

    Catatan

    Memblokir pembaca baru ketika penulis diantrekan adalah kebijakan kewajaran kunci yang mendukung penulis. Kebijakan kewajaran saat ini menyeimbangkan kewajaran kepada pembaca dan penulis, untuk mempromosikan throughput dalam skenario yang paling umum. Versi .NET di masa mendatang dapat memperkenalkan kebijakan kewajaran baru.

  • Utas yang mencoba memasukkan blok mode yang dapat ditingkatkan jika sudah ada utas dalam mode yang dapat ditingkatkan, jika ada utas yang menunggu untuk memasuki mode tulis, atau jika ada satu utas dalam mode tulis.

  • Utas yang mencoba memasukkan blok mode tulis jika ada utas di salah satu dari tiga mode.

Memutakhirkan dan menurunkan tingkat kunci

Mode yang dapat ditingkatkan ditujukan untuk kasus di mana utas biasanya dibaca dari sumber daya yang dilindungi, tetapi mungkin perlu menulisnya jika beberapa kondisi terpenuhi. Utas yang telah memasuki mode yang dapat ditingkatkan ReaderWriterLockSlim memiliki akses baca ke sumber daya yang dilindungi, dan dapat meningkatkan ke mode tulis dengan memanggil EnterWriteLock metode atau TryEnterWriteLock . Karena hanya ada satu utas dalam mode yang dapat ditingkatkan pada satu waktu, peningkatan ke mode tulis tidak dapat mengalami kebuntuan ketika rekursi tidak diizinkan, yang merupakan kebijakan default.

Penting

Terlepas dari kebijakan rekursi, utas yang awalnya memasuki mode baca tidak diizinkan untuk meningkatkan ke mode yang dapat ditingkatkan atau mode tulis, karena pola tersebut menciptakan kemungkinan kebuntuan yang kuat. Misalnya, jika dua utas dalam mode baca keduanya mencoba memasuki mode tulis, mereka akan kebuntuan. Mode yang dapat ditingkatkan dirancang untuk menghindari kebuntuan tersebut.

Jika ada utas lain dalam mode baca, utas yang meningkatkan blok. Saat utas diblokir, utas lain yang mencoba memasuki mode baca diblokir. Ketika semua utas telah keluar dari mode baca, utas yang dapat ditingkatkan yang diblokir memasuki mode tulis. Jika ada utas lain yang menunggu untuk memasuki mode tulis, utas tersebut tetap diblokir, karena utas tunggal yang dalam mode yang dapat ditingkatkan mencegah mereka mendapatkan akses eksklusif ke sumber daya.

Ketika utas dalam mode yang dapat ditingkatkan keluar dari mode tulis, utas lain yang menunggu untuk memasuki mode baca dapat melakukannya, kecuali ada utas yang menunggu untuk memasuki mode tulis. Utas dalam mode yang dapat ditingkatkan dapat ditingkatkan dan diturunkan tanpa batas waktu, selama itu adalah satu-satunya utas yang menulis ke sumber daya yang dilindungi.

Penting

Jika Anda mengizinkan beberapa utas untuk memasuki mode tulis atau mode yang dapat ditingkatkan, Anda tidak boleh mengizinkan satu utas untuk memonopoli mode yang dapat ditingkatkan. Jika tidak, utas yang mencoba memasuki mode tulis secara langsung akan diblokir tanpa batas waktu, dan saat diblokir, utas lain tidak akan dapat memasuki mode baca.

Utas dalam mode yang dapat ditingkatkan dapat diturunkan tingkatnya ke mode baca dengan terlebih dahulu memanggil EnterReadLock metode lalu memanggil ExitUpgradeableReadLock metode . Pola penurunan tingkat ini diizinkan untuk semua kebijakan pengulangan kunci, bahkan NoRecursion.

Setelah menurunkan ke mode baca, utas tidak dapat masuk kembali ke mode yang dapat ditingkatkan hingga keluar dari mode baca.

Masukkan kunci secara rekursif

Anda dapat membuat ReaderWriterLockSlim yang mendukung entri kunci rekursif dengan menggunakan ReaderWriterLockSlim(LockRecursionPolicy) konstruktor yang menentukan kebijakan penguncian, dan menentukan LockRecursionPolicy.SupportsRecursion.

Catatan

Penggunaan rekursi tidak disarankan untuk pengembangan baru, karena memperkenalkan komplikasi yang tidak perlu dan membuat kode Anda lebih rentan terhadap kebuntuan.

ReaderWriterLockSlim Untuk yang memungkinkan rekursi, berikut ini dapat dikatakan tentang mode yang dapat dimasukkan utas:

  • Utas dalam mode baca dapat memasuki mode baca secara rekursif, tetapi tidak dapat memasuki mode tulis atau mode yang dapat ditingkatkan. Jika mencoba melakukan ini, akan LockRecursionException dilemparkan. Memasuki mode baca lalu memasuki mode tulis atau mode yang dapat ditingkatkan adalah pola dengan probabilitas kebuntuan yang kuat, sehingga tidak diperbolehkan. Seperti yang dibahas sebelumnya, mode yang dapat ditingkatkan disediakan untuk kasus di mana perlu untuk meningkatkan kunci.

  • Utas dalam mode yang dapat ditingkatkan dapat memasuki mode tulis dan/atau mode baca, dan dapat memasuki salah satu dari tiga mode secara rekursif. Namun, upaya untuk memasukkan blok mode tulis jika ada utas lain dalam mode baca.

  • Utas dalam mode tulis dapat memasuki mode baca dan/atau mode yang dapat ditingkatkan, dan dapat memasuki salah satu dari tiga mode secara rekursif.

  • Utas yang belum memasuki kunci dapat memasuki mode apa pun. Upaya ini dapat memblokir karena alasan yang sama dengan upaya untuk memasukkan kunci non-rekursif.

Utas dapat keluar dari mode yang telah dimasukkan dalam urutan apa pun, selama keluar dari setiap mode tepat sebanyak yang memasuki mode tersebut. Jika utas mencoba keluar dari mode terlalu berkali-kali, atau untuk keluar dari mode yang belum dimasukkan, utas SynchronizationLockException akan dilemparkan.

Mengunci status

Anda mungkin merasa berguna untuk memikirkan kunci dalam hal statusnya. ReaderWriterLockSlim Dapat berada di salah satu dari empat status: tidak dimasukkan, dibaca, ditingkatkan, dan ditulis.

  • Tidak dimasukkan: Dalam status ini, tidak ada utas yang memasuki kunci (atau semua utas telah keluar dari kunci).

  • Baca: Dalam status ini, satu atau beberapa utas telah memasukkan kunci untuk akses baca ke sumber daya yang dilindungi.

    Catatan

    Utas dapat memasuki kunci dalam mode baca dengan menggunakan EnterReadLock metode atau TryEnterReadLock , atau dengan menurunkan dari mode yang dapat ditingkatkan.

  • Peningkatan: Dalam status ini, satu utas telah memasuki kunci untuk akses baca dengan opsi untuk meningkatkan akses tulis (yaitu, dalam mode yang dapat ditingkatkan), dan nol atau lebih utas telah memasuki kunci untuk akses baca. Tidak lebih dari satu utas pada satu waktu dapat memasukkan kunci dengan opsi untuk meningkatkan; utas tambahan yang mencoba memasukkan mode yang dapat ditingkatkan diblokir.

  • Tulis: Dalam status ini, satu utas telah memasuki kunci untuk akses tulis ke sumber daya yang dilindungi. Utas itu memiliki kunci eksklusif. Utas lain yang mencoba memasukkan kunci karena alasan apa pun diblokir.

Tabel berikut ini menjelaskan transisi antara status kunci, untuk kunci yang tidak mengizinkan pengulangan, saat utas t mengambil tindakan yang dijelaskan di kolom paling kiri. Pada saat mengambil tindakan, t tidak memiliki mode. (Kasus khusus di mana t berada dalam mode yang dapat ditingkatkan dijelaskan dalam catatan kaki tabel.) Baris atas menjelaskan status awal kunci. Sel menjelaskan apa yang terjadi pada utas, dan memperlihatkan perubahan pada status kunci dalam tanda kurung.

Transisi Tidak dimasukkan (N) Baca (R) Peningkatan (U) Tulis (W)
t memasuki mode baca t masukkan (R). t memblokir jika utas menunggu mode tulis; jika tidak, t masukkan. t memblokir jika utas menunggu mode tulis; jika tidak, t masukkan.1 t Blok.
t memasuki mode yang dapat ditingkatkan t masukkan (U). t memblokir jika utas menunggu mode tulis atau mode peningkatan; jika tidak, t masukkan (U). t Blok. t Blok.
t memasuki mode tulis t masukkan (W). t Blok. t Blok.2 t Blok.

1 Jika t dimulai dalam mode yang dapat ditingkatkan, ia memasuki mode baca. Tindakan ini tidak pernah memblokir. Status kunci tidak berubah. (Utas kemudian dapat menyelesaikan downgrade ke mode baca dengan keluar dari mode yang dapat ditingkatkan.)

2 Jika t dimulai dalam mode yang dapat ditingkatkan, alur akan diblokir jika ada utas dalam mode baca. Jika tidak, ini akan ditingkatkan ke mode tulis. Status kunci berubah menjadi Tulis (W). Jika t memblokir karena ada utas dalam mode baca, maka akan memasuki mode tulis segera setelah utas terakhir keluar dari mode baca, bahkan jika ada utas yang menunggu untuk memasuki mode tulis.

Ketika perubahan status terjadi karena utas keluar dari kunci, utas berikutnya yang akan dibangunkan dipilih sebagai berikut:

  • Pertama, utas yang menunggu mode tulis dan sudah dalam mode yang dapat ditingkatkan (mungkin ada paling banyak satu utas tersebut).
  • Gagal itu, utas yang menunggu mode tulis.
  • Gagal itu, utas yang menunggu mode yang dapat ditingkatkan.
  • Gagal itu, semua utas yang menunggu mode baca.

Status kunci berikutnya selalu Tulis (W) dalam dua kasus pertama dan Tingkatkan (U) dalam kasus ketiga, terlepas dari status kunci ketika utas keluar memicu perubahan status. Dalam kasus terakhir, status kunci adalah Peningkatan (U) jika ada utas dalam mode yang dapat ditingkatkan setelah perubahan status, dan Baca (R) jika tidak, terlepas dari status sebelumnya.

Contoh

Contoh berikut menunjukkan cache sederhana yang disinkronkan yang menyimpan string dengan kunci bilangan bulat. Instans digunakan ReaderWriterLockSlim untuk menyinkronkan akses ke Dictionary<TKey,TValue> yang berfungsi sebagai cache dalam.

Contohnya mencakup metode sederhana untuk ditambahkan ke cache, menghapus dari cache, dan membaca dari cache. Untuk menunjukkan waktu habis, contohnya menyertakan metode yang ditambahkan ke cache hanya jika dapat melakukannya dalam waktu habis yang ditentukan.

Untuk menunjukkan mode yang dapat ditingkatkan, contohnya mencakup metode yang mengambil nilai yang terkait dengan kunci dan membandingkannya dengan nilai baru. Jika nilai tidak berubah, metode mengembalikan status yang menunjukkan tidak ada perubahan. Jika tidak ada nilai yang ditemukan untuk kunci, pasangan kunci/nilai akan disisipkan. Jika nilai telah berubah, nilai akan diperbarui. Mode yang dapat ditingkatkan memungkinkan utas untuk meningkatkan dari akses baca ke akses tulis sesuai kebutuhan, tanpa risiko kebuntuan.

Contohnya mencakup enumerasi berlapis yang menentukan nilai pengembalian untuk metode yang menunjukkan mode yang dapat ditingkatkan.

Contoh menggunakan konstruktor tanpa parameter untuk membuat kunci, sehingga rekursi tidak diizinkan. Pemrograman ReaderWriterLockSlim lebih sederhana dan kurang rentan terhadap kesalahan ketika kunci tidak memungkinkan pengulangan.

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

Kode berikut kemudian menggunakan SynchronizedCache objek untuk menyimpan kamus nama sayuran. Ini membuat tiga tugas. Yang pertama menulis nama sayuran yang disimpan dalam array ke SynchronizedCache instans. Tugas kedua dan ketiga menampilkan nama sayuran, yang pertama dalam urutan naik (dari indeks rendah ke indeks tinggi), yang kedua dalam urutan menurun. Tugas akhir mencari string "mentimun" dan, ketika menemukannya, memanggil EnterUpgradeableReadLock metode untuk mengganti string "kacang hijau".

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