다음을 통해 공유


System.Threading.ReaderWriterLockSlim 클래스

이 문서에서는 이 API에 대한 참조 설명서에 대한 추가 설명서를 제공합니다.

여러 스레드에서 읽고 한 번에 하나의 스레드에 의해 기록되는 리소스를 보호하는 데 사용합니다 ReaderWriterLockSlim . ReaderWriterLockSlim 를 사용하면 여러 스레드가 읽기 모드에 있고, 한 스레드가 잠금의 단독 소유권이 있는 쓰기 모드에 있을 수 있으며, 읽기 액세스 권한이 있는 하나의 스레드가 업그레이드 가능한 읽기 모드로 전환할 수 있습니다. 이 스레드는 리소스에 대한 읽기 액세스를 포기하지 않고도 쓰기 모드로 업그레이드할 수 있습니다.

참고 항목

기본적으로 새 인스턴스는 ReaderWriterLockSlim 플래그를 사용하여 LockRecursionPolicy.NoRecursion 생성되며 재귀를 허용하지 않습니다. 재귀로 인해 불필요한 복잡성이 발생하고 코드가 교착 상태가 발생하기 쉽기 때문에 이 기본 정책은 모든 새 개발에 권장됩니다. 사용하거나 ReaderWriterLock사용하는 Monitor 기존 프로젝트에서 마이그레이션을 간소화하려면 플래그를 LockRecursionPolicy.SupportsRecursion 사용하여 재귀를 허용하는 인스턴스를 ReaderWriterLockSlim 만들 수 있습니다.

스레드는 읽기 모드, 쓰기 모드 및 업그레이드 가능한 읽기 모드의 세 가지 모드로 잠금을 입력할 수 있습니다. (이 항목의 나머지 단계에서는 "업그레이드 가능한 읽기 모드"를 "업그레이드 가능한 모드"라고 하며, "enter x mode"라는 구는 "모드에서 잠금 입력"이라는 긴 구를 기본 설정으로 x 사용합니다.)

재귀 정책에 관계없이 언제든지 하나의 스레드만 쓰기 모드에 있을 수 있습니다. 스레드가 쓰기 모드인 경우 다른 스레드는 어떤 모드에서도 잠금을 입력할 수 없습니다. 한 번에 하나의 스레드만 업그레이드 가능한 모드에 있을 수 있습니다. 임의의 스레드 수는 읽기 모드일 수 있으며, 다른 스레드가 읽기 모드에 있는 동안 업그레이드 가능한 모드에 스레드가 하나 있을 수 있습니다.

Important

이 형식이 구현 하는 IDisposable 인터페이스입니다. 형식을 사용 하 여 마쳤으면 직접 또는 간접적으로의 삭제 해야 있습니다. 직접 형식의 dispose 호출 해당 Dispose 의 메서드를 try/catch 블록입니다. 삭제 하지 직접, 언어 구문 같은 사용 using (C#에서) 또는 Using (Visual Basic에서는). 자세한 내용은 "를 사용 하는 개체는 구현 IDisposable" 섹션을 참조 하세요.를 IDisposable 인터페이스 항목입니다.

ReaderWriterLockSlim 에는 관리되는 스레드 선호도가 있습니다. 즉, 각 Thread 개체는 잠금 모드를 입력하고 종료하기 위해 자체 메서드 호출을 수행해야 합니다. 스레드는 다른 스레드의 모드를 변경할 수 없습니다.

재귀를 ReaderWriterLockSlim 허용하지 않는 경우 잠금을 입력하려는 스레드는 다음과 같은 여러 가지 이유로 차단할 수 있습니다.

  • 쓰기 모드로 전환하기 위해 대기하는 스레드가 있거나 쓰기 모드에 단일 스레드가 있는 경우 읽기 모드 블록으로 들어가려고 하는 스레드입니다.

    참고 항목

    작성자가 대기 중일 때 새 판독기를 차단하는 것은 작성자를 선호하는 잠금 공정성 정책입니다. 현재의 공정성 정책은 가장 일반적인 시나리오에서 처리량을 촉진하기 위해 독자와 작가에게 공정성을 분산합니다. 이후 버전의 .NET에서는 새로운 공정성 정책을 도입할 수 있습니다.

  • 업그레이드 가능한 모드에 스레드가 이미 있거나, 쓰기 모드로 전환하기 위해 대기 중인 스레드가 있거나, 쓰기 모드에 단일 스레드가 있는 경우 업그레이드 가능한 모드로 전환하려는 스레드가 차단됩니다.

  • 세 가지 모드 중 한 가지 모드에 스레드가 있는 경우 쓰기 모드 블록으로 들어가려고 하는 스레드입니다.

잠금 업그레이드 및 다운그레이드

업그레이드 가능 모드는 스레드가 일반적으로 보호된 리소스에서 읽지만 일부 조건이 충족되는 경우 이를 작성해야 하는 경우를 위한 것입니다. 업그레이드 가능한 모드로 ReaderWriterLockSlim 전환된 스레드는 보호된 리소스에 대한 읽기 액세스 권한을 가지며, 또는 TryEnterWriteLock 메서드를 호출 EnterWriteLock 하여 쓰기 모드로 업그레이드할 수 있습니다. 업그레이드 가능한 모드에는 한 번에 하나의 스레드만 있을 수 있으므로 재귀가 허용되지 않을 때 쓰기 모드로 업그레이드하면 교착 상태가 될 수 없습니다. 이것이 기본 정책입니다.

Important

재귀 정책에 관계없이 처음에 읽기 모드로 전환된 스레드는 업그레이드 가능한 모드 또는 쓰기 모드로 업그레이드할 수 없습니다. 이 패턴은 교착 상태의 강력한 확률을 생성하기 때문입니다. 예를 들어 읽기 모드의 두 스레드가 모두 쓰기 모드로 전환하려고 하면 교착 상태가 발생합니다. 업그레이드 가능한 모드는 이러한 교착 상태를 방지하도록 설계되었습니다.

읽기 모드에 다른 스레드가 있는 경우 블록을 업그레이드하는 스레드입니다. 스레드가 차단되는 동안 읽기 모드로 전환하려는 다른 스레드는 차단됩니다. 모든 스레드가 읽기 모드에서 종료되면 차단된 업그레이드 가능한 스레드가 쓰기 모드로 전환됩니다. 다른 스레드가 쓰기 모드로 전환되기를 기다리는 경우 업그레이드 가능한 모드에 있는 단일 스레드가 리소스에 대한 배타적인 액세스 권한을 얻을 수 없으므로 다시 기본 차단됩니다.

업그레이드 가능한 모드의 스레드가 쓰기 모드를 종료하면 쓰기 모드로 전환하기 위해 대기 중인 스레드가 없는 한 읽기 모드로 전환되기를 기다리는 다른 스레드가 이 작업을 수행할 수 있습니다. 업그레이드 가능한 모드의 스레드는 보호된 리소스에 쓰는 유일한 스레드인 한 무기한 업그레이드 및 다운그레이드할 수 있습니다.

Important

여러 스레드가 쓰기 모드 또는 업그레이드 가능 모드로 전환되도록 허용하는 경우 한 스레드가 업그레이드 가능 모드를 독점하도록 허용해서는 안 됩니다. 그렇지 않으면 직접 쓰기 모드로 전환하려는 스레드가 무기한 차단되고 차단되는 동안 다른 스레드는 읽기 모드로 전환할 수 없습니다.

업그레이드 가능한 모드의 스레드는 먼저 메서드를 호출한 다음 메서드를 호출 EnterReadLock 하여 읽기 모드로 다운그레이드할 ExitUpgradeableReadLock 수 있습니다. 이 다운그레이드 패턴은 모든 잠금 재귀 정책에 NoRecursion도 허용됩니다.

읽기 모드로 다운그레이드한 후에는 스레드가 읽기 모드에서 종료될 때까지 업그레이드 가능한 모드를 다시 입력할 수 없습니다.

잠금을 재귀적으로 입력합니다.

잠금 정책을 지정하는 생성자를 사용하고 ReaderWriterLockSlim(LockRecursionPolicy) 지정하여 재귀 잠금 항목을 지원하는 항목을 만들 ReaderWriterLockSlim 수 있습니다LockRecursionPolicy.SupportsRecursion.

참고 항목

불필요한 복잡성을 유발하고 코드가 교착 상태에 빠지기 쉽기 때문에 재귀를 사용하는 것은 새로운 개발에 권장되지 않습니다.

ReaderWriterLockSlim 재귀를 허용하는 경우 스레드가 입력할 수 있는 모드에 대해 다음을 확인할 수 있습니다.

  • 읽기 모드의 스레드는 재귀적으로 읽기 모드로 전환할 수 있지만 쓰기 모드 또는 업그레이드 가능한 모드로 전환할 수는 없습니다. 이 작업을 수행하려고 하면 throw LockRecursionException 됩니다. 읽기 모드를 입력한 다음 쓰기 모드 또는 업그레이드 가능 모드를 입력하는 것은 교착 상태의 가능성이 높은 패턴이므로 허용되지 않습니다. 앞에서 설명한 대로 잠금을 업그레이드해야 하는 경우 업그레이드 가능한 모드가 제공됩니다.

  • 업그레이드 가능한 모드의 스레드는 쓰기 모드 및/또는 읽기 모드로 전환할 수 있으며 세 가지 모드 중 한 가지 모드를 재귀적으로 입력할 수 있습니다. 그러나 읽기 모드에 다른 스레드가 있는 경우 쓰기 모드로 전환하려고 하면 블록이 차단됩니다.

  • 쓰기 모드의 스레드는 읽기 모드 및/또는 업그레이드 가능 모드로 전환할 수 있으며, 세 가지 모드 중 한 가지 모드를 재귀적으로 입력할 수 있습니다.

  • 잠금에 들어가지 않은 스레드는 모든 모드로 전환할 수 있습니다. 이 시도는 재귀적이지 않은 잠금을 입력하려는 시도와 동일한 이유로 차단할 수 있습니다.

스레드는 해당 모드를 입력한 횟수만큼 정확하게 각 모드를 종료하는 한 순서에 따라 입력한 모드를 종료할 수 있습니다. 스레드가 모드를 너무 여러 번 종료하거나 입력되지 않은 모드를 종료하려고 하면 throw SynchronizationLockException 됩니다.

잠금 상태

상태 측면에서 잠금을 생각하는 것이 유용할 수 있습니다. A ReaderWriterLockSlim 는 입력, 읽기, 업그레이드 및 쓰기가 아닌 네 가지 상태 중 하나일 수 있습니다.

  • 입력되지 않음: 이 상태에서는 스레드가 잠금에 들어가지 않았거나 모든 스레드가 잠금을 종료했습니다.

  • 읽기: 이 상태에서 하나 이상의 스레드가 보호된 리소스에 대한 읽기 액세스에 대한 잠금을 입력했습니다.

    참고 항목

    스레드는 또는 메서드를 사용 EnterReadLock 하거나 TryEnterReadLock 업그레이드 가능한 모드에서 다운그레이드하여 읽기 모드로 잠금을 입력할 수 있습니다.

  • 업그레이드: 이 상태에서는 한 스레드가 쓰기 액세스로 업그레이드하는 옵션(즉, 업그레이드 가능 모드)을 사용하여 읽기 액세스 잠금에 들어갔고, 0개 이상의 스레드가 읽기 액세스 잠금에 들어갔습니다. 한 번에 두 개 이상의 스레드가 업그레이드 옵션으로 잠금에 들어갈 수 없습니다. 업그레이드 가능한 모드로 전환하려는 추가 스레드가 차단됩니다.

  • 쓰기: 이 상태에서 한 스레드가 보호된 리소스에 대한 쓰기 액세스에 대한 잠금을 입력했습니다. 해당 스레드는 잠금을 독점적으로 소유합니다. 어떤 이유로든 잠금을 입력하려고 시도하는 다른 스레드는 차단됩니다.

다음 표에서는 스레드 t 가 맨 왼쪽 열에 설명된 작업을 수행할 때 재귀를 허용하지 않는 잠금의 잠금 상태 간 전환을 설명합니다. 작업을 t 수행할 때 모드가 없습니다. (업그레이드 가능 모드인 특수한 경우 t 는 표 각주에 설명되어 있습니다.) 맨 위 행은 잠금의 시작 상태를 설명합니다. 셀은 스레드에 발생하는 작업을 설명하고 잠금 상태의 변경 내용을 괄호로 표시합니다.

전환 입력 안 됨(N) 읽기(R) 업그레이드(U) 쓰기(W)
t 읽기 모드로 전환 t enters(R). t 스레드가 쓰기 모드를 기다리는 경우 를 차단합니다. 그렇지 않으면 t 입력합니다. t 스레드가 쓰기 모드를 기다리는 경우 를 차단합니다. 그렇지 않으면 t 입력합니다.1 t 블록.
t 업그레이드 가능 모드로 전환 t enters(U)입니다. t 스레드가 쓰기 모드 또는 업그레이드 모드를 기다리는 경우 을 차단합니다. 그렇지 않으면 t (U)를 입력합니다. t 블록. t 블록.
t 쓰기 모드로 전환 t enters(W). t 블록. t 블록.2 t 블록.

1t 업그레이드 가능 모드에서 시작하면 읽기 모드로 전환됩니다. 이 작업은 차단되지 않습니다. 잠금 상태는 변경되지 않습니다. 그러면 스레드가 업그레이드 가능한 모드를 종료하여 읽기 모드로 다운그레이드를 완료할 수 있습니다.

2 업그레이드 가능한 모드에서 시작하는 경우 t 읽기 모드에 스레드가 있는 경우 차단됩니다. 그렇지 않으면 쓰기 모드로 업그레이드됩니다. 잠금 상태가 쓰기(W)로 변경됩니다. 읽기 모드에 스레드가 있기 때문에 차단되는 경우 t 쓰기 모드로 전환되기 위해 대기 중인 스레드가 있더라도 마지막 스레드가 읽기 모드를 종료하는 즉시 쓰기 모드로 전환됩니다.

스레드가 잠금을 종료하여 상태가 변경되면 다음 스레드가 다음과 같이 선택됩니다.

  • 첫째, 쓰기 모드를 기다리고 있으며 업그레이드 가능한 모드에 있는 스레드입니다(이러한 스레드는 최대 하나일 수 있습니다).
  • 이 오류는 쓰기 모드를 기다리는 스레드입니다.
  • 실패합니다. 업그레이드 가능한 모드를 기다리는 스레드입니다.
  • 실패하면 읽기 모드를 기다리는 모든 스레드가 발생합니다.

잠금의 후속 상태는 종료 스레드가 상태 변경을 트리거한 경우 잠금 상태에 관계없이 처음 두 경우의 경우 항상 쓰기(W)이고 세 번째 경우에는 업그레이드(U)입니다. 마지막 경우 상태 변경 후 업그레이드 가능한 모드에 스레드가 있는 경우 잠금 상태는 업그레이드(U)이고, 그렇지 않으면 이전 상태에 관계없이 읽기(R)입니다.

예제

다음 예제에서는 정수 키가 있는 문자열을 보유하는 간단한 동기화 캐시를 보여 줍니다. 인스턴스 ReaderWriterLockSlim 는 내부 캐시 역할을 하는 액세스 권한을 동기화하는 Dictionary<TKey,TValue> 데 사용됩니다.

이 예제에는 캐시에 추가하고, 캐시에서 삭제하고, 캐시에서 읽는 간단한 메서드가 포함되어 있습니다. 시간 초과를 보여주기 위해 이 예제에는 지정된 제한 시간 내에 캐시를 추가할 수 있는 경우에만 캐시에 추가하는 메서드가 포함됩니다.

업그레이드 가능한 모드를 보여 주는 예제에는 키와 연결된 값을 검색하고 새 값과 비교하는 메서드가 포함되어 있습니다. 값이 변경되지 않은 경우 메서드는 변경 사항을 나타내는 상태 반환합니다. 키에 대한 값을 찾을 수 없으면 키/값 쌍이 삽입됩니다. 값이 변경되면 업데이트됩니다. 업그레이드 가능 모드를 사용하면 스레드가 교착 상태의 위험 없이 필요에 따라 읽기 액세스에서 쓰기 액세스로 업그레이드할 수 있습니다.

이 예제에는 업그레이드 가능한 모드를 보여 주는 메서드의 반환 값을 지정하는 중첩된 열거형이 포함되어 있습니다.

이 예제에서는 매개 변수가 없는 생성자를 사용하여 잠금을 만들므로 재귀가 허용되지 않습니다. 잠금에서 ReaderWriterLockSlim 재귀를 허용하지 않는 경우 프로그래밍이 더 간단하고 오류가 발생하기 쉽습니다.

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

다음 코드는 개체를 SynchronizedCache 사용하여 야채 이름의 사전을 저장합니다. 세 가지 작업을 만듭니다. 첫 번째는 배열에 저장된 채소의 이름을 인스턴스에 SynchronizedCache 씁니다. 두 번째 및 세 번째 작업은 채소의 이름을 표시하고, 첫 번째 작업은 오름차순(낮은 인덱스에서 높은 인덱스로), 두 번째는 내림차순으로 표시합니다. 마지막 작업은 문자열 "오이"를 검색하고 이를 찾으면 메서드를 호출 EnterUpgradeableReadLock 하여 문자열 "녹색 콩"을 대체합니다.

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