Bagikan melalui


Kelas System.Threading.ReaderWriterLockSlim

Nota

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.

Nota

Secara bawaan, instans baru dari ReaderWriterLockSlim dibuat dengan parameter LockRecursionPolicy.NoRecursion 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 sisa topik ini, "mode baca yang dapat ditingkatkan" disebut sebagai "mode yang dapat ditingkatkan", dan frasa "masuk ke mode x" digunakan daripada frasa yang lebih panjang "masuk ke kunci dalam mode x".)

Terlepas dari kebijakan rekursi, hanya satu utas yang dapat berada dalam mode tulis pada satu waktu. Saat utas dalam mode tulis, tidak ada utas lain yang dapat memasuki kunci dalam mode apa pun. Hanya satu utas yang dapat berada dalam mode peningkatan 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 antarmuka IDisposable. Setelah selesai menggunakan jenis tersebut, Anda harus menyingkirkannya baik secara langsung maupun tidak langsung. Untuk meniadakan tipe secara langsung, panggil metode Dispose dalam blok try/catch. Untuk membuangnya secara tidak langsung, gunakan konstruksi bahasa seperti using (dalam C#) atau Using (di Visual Basic). Untuk informasi selengkapnya, lihat bagian "Menggunakan Objek yang Menerapkan IDisposable" dalam IDisposable topik antarmuka.

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

Jika sebuah ReaderWriterLockSlim tidak mengizinkan rekursi, utas yang mencoba memasuki kunci dapat terblokir karena beberapa alasan:

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

    Nota

    Menghalangi pembaca baru ketika penulis sedang antri adalah kebijakan mengunci yang mendukung penulis. Kebijakan keadilan saat ini menyeimbangkan keadilan bagi pembaca dan penulis, untuk meningkatkan kapasitas pemrosesan dalam skenario yang paling sering terjadi. Versi .NET di masa mendatang dapat memperkenalkan kebijakan kewajaran baru.

  • Utas yang mencoba memasuki mode yang dapat ditingkatkan akan terblokir jika sudah ada satu 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 masuk ke mode tulis akan terblokir jika ada utas di salah satu dari tiga mode lainnya.

Memutakhirkan dan menurunkan status kunci

Mode yang dapat ditingkatkan ditujukan untuk kasus di mana utas biasanya membaca dari sumber daya yang dilindungi, tetapi mungkin perlu menulis ke sumber tersebut jika beberapa kondisi terpenuhi. Utas yang telah memasuki mode yang dapat ditingkatkan dengan ReaderWriterLockSlim memiliki akses baca ke sumber daya yang dilindungi, dan dapat ditingkatkan ke mode tulis dengan memanggil fungsi EnterWriteLock 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 di-upgrade keluar dari mode penulisan, utas lain yang menunggu untuk memasuki mode baca dapat melakukannya, kecuali ada utas yang menunggu untuk memasuki mode penulisan. 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 diupgrade dapat diturunkan ke mode baca dengan terlebih dahulu memanggil metode EnterReadLock lalu memanggil metode ExitUpgradeableReadLock. 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.

Nota

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

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

  • Utas dalam mode baca dapat memasuki mode baca secara rekursif, tetapi tidak dapat memasuki mode tulis atau mode yang dapat ditingkatkan. Jika itu mencoba melakukan ini, sebuah LockRecursionException akan muncul. 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 memasuki mode tulis akan terblokir 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 terblokir karena alasan yang sama dengan upaya untuk memasuki kunci non-rekursif.

Thread dapat keluar dari mode yang telah dimasuki dalam urutan apa pun, selama keluar dari setiap mode tepat sebanyak jumlah saat memasuki mode tersebut. Jika sebuah utas mencoba keluar dari mode terlalu sering, atau mencoba keluar dari mode yang belum dimasuki, maka SynchronizationLockException akan dilemparkan.

Status kunci

Anda mungkin merasa berguna untuk memikirkan kunci berdasarkan kondisi atau keadaan yang dimilikinya. ReaderWriterLockSlim dapat berada di salah satu dari empat status: belum dimasukkan, dibaca, diupgrade, 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.

    Nota

    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 memasuki kunci dengan opsi peningkatan; utas tambahan yang mencoba memasuki mode peningkatan akan diblokir.

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

Tabel berikut ini menjelaskan transisi antara status kunci, untuk kunci yang tidak mengizinkan pengulangan, ketika utas t melakukan tindakan yang dijelaskan di kolom kiri paling ujung. 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) Pemutakhiran (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-blok.
t memasuki mode yang dapat ditingkatkan t masukkan (U). t memblokir jika utas menunggu mode tulis atau mode peningkatan; jika tidak, t memasuki (U). t Blok-blok. t Blok-blok.
t memasuki mode menulis t masukkan (W). t Blok-blok. t Blok-blok. 2 t Blok-blok.

1 Jika t dimulai dalam mode yang dapat ditingkatkan, ia memasuki mode baca. Tindakan ini tidak pernah menghalangi. 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).
  • Kalau begitu, utas yang sedang menunggu dalam mode penulisan.
  • Jika itu gagal, utas yang sedang menunggu mode peningkatan.
  • Jika tidak, semua utas yang menunggu mode baca.

Status/keadaan kunci berikutnya selalu Tulis (W) dalam dua kasus pertama dan Tingkatkan (U) dalam kasus ketiga, apapun status kunci saat utas keluar memicu perubahan status. Dalam kasus terakhir, status kunci adalah Upgrade (U) jika ada utas dalam mode yang dapat di-upgrade setelah perubahan status, dan Baca (R) jika tidak, tanpa mempedulikan status sebelumnya.

Contoh

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

Contohnya mencakup metode sederhana untuk ditambahkan ke cache, menghapus dari cache, dan membaca dari cache. Untuk menunjukkan batas waktu, contohnya menyertakan metode yang menambahkan ke dalam cache hanya jika dapat melakukannya dalam batas waktu 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 thread untuk beralih dari akses baca ke akses tulis sesuai kebutuhan, tanpa risiko deadlock.

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 menuliskan nama sayuran yang disimpan dalam array ke instance SynchronizedCache. 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 {Task.CurrentId} wrote {itemsWritten} items\n");
                                } ));
      // 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 {Task.CurrentId} read {items} items: {output}\n");
                                     } 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($"   {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