System.Threading.ReaderWriterLockSlim, klasa
Ten artykuł zawiera dodatkowe uwagi dotyczące dokumentacji referencyjnej dla tego interfejsu API.
Służy ReaderWriterLockSlim do ochrony zasobu, który jest odczytywany przez wiele wątków i zapisywany przez jeden wątek naraz. ReaderWriterLockSlim umożliwia korzystanie z wielu wątków w trybie odczytu, umożliwia jednemu wątkowi tryb zapisu z wyłączną własnością blokady i umożliwia korzystanie z jednego wątku, który ma dostęp do odczytu w trybie odczytu, z którego wątek może zostać uaktualniony do trybu zapisu bez konieczności wykluczania dostępu do odczytu do zasobu.
Uwaga
- ReaderWriterLockSlim jest podobny do ReaderWriterLock, ale ma uproszczone reguły rekursji i uaktualniania i obniżania stanu blokady. ReaderWriterLockSlim unika wielu przypadków potencjalnego zakleszczenia. Ponadto wydajność funkcji ReaderWriterLockSlim jest znacznie lepsza niż ReaderWriterLock. ReaderWriterLockSlim jest zalecana dla wszystkich nowych rozwiązań.
- ReaderWriterLockSlim nie jest bezpieczny wątkowo. Nie należy używać go w środowisku, w którym można przerwać dostęp wątków, takich jak .NET Framework. Jeśli używasz platformy .NET Core lub .NET 5+, powinno to być w porządku. Abort program nie jest obsługiwany na platformie .NET Core i jest przestarzały w wersjach .NET 5 i nowszych.
Domyślnie nowe wystąpienia programu ReaderWriterLockSlim są tworzone z flagą LockRecursionPolicy.NoRecursion i nie zezwalają na rekursję. Te domyślne zasady są zalecane dla wszystkich nowych programowania, ponieważ rekursja wprowadza niepotrzebne komplikacje i sprawia, że kod jest bardziej podatny na zakleszczenia. Aby uprościć migrację z istniejących projektów korzystających Monitor z programu lub ReaderWriterLock, możesz użyć LockRecursionPolicy.SupportsRecursion flagi , aby utworzyć wystąpienia ReaderWriterLockSlim , które zezwalają na rekursję.
Wątek może wprowadzać blokadę w trzech trybach: tryb odczytu, tryb zapisu i tryb odczytu z możliwością uaktualnienia. (W pozostałej części tego tematu "tryb odczytu z możliwością uaktualnienia" jest określany jako "tryb uaktualniania", a fraza "enter mode" jest używana w preferencjach do dłuższej frazy "enter x
the lock in x
mode".
Niezależnie od zasad rekursji tylko jeden wątek może być w trybie zapisu w dowolnym momencie. Gdy wątek jest w trybie zapisu, żaden inny wątek nie może wejść do blokady w dowolnym trybie. Tylko jeden wątek może być w trybie uaktualniania w dowolnym momencie. Dowolna liczba wątków może być w trybie odczytu i może istnieć jeden wątek w trybie uaktualniania, podczas gdy inne wątki są w trybie odczytu.
Ważne
Ten typ implementuje IDisposable interfejs. Po zakończeniu korzystania z typu należy usunąć go bezpośrednio lub pośrednio. Aby usunąć typ bezpośrednio, wywołaj metodę Disposetry
/catch
w bloku. Aby usunąć go pośrednio, należy użyć konstrukcji języka, takiej jak using
(w języku C#) lub Using
(w Visual Basic). Aby uzyskać więcej informacji, zobacz sekcję "Using an Object that Implements IDisposable" (Używanie obiektu implementujące interfejs IDisposable) w temacie interfejsu IDisposable .
ReaderWriterLockSlim ma zarządzaną koligację wątków; oznacza to, że każdy Thread obiekt musi wykonywać własne wywołania metody w celu wprowadzania i zamykania trybów blokady. Żaden wątek nie może zmienić trybu innego wątku.
Jeśli obiekt ReaderWriterLockSlim nie zezwala na rekursję, wątek, który próbuje wprowadzić blokadę, może zablokować z kilku powodów:
Wątek, który próbuje wprowadzić bloki trybu odczytu, jeśli istnieją wątki oczekujące na wejście w tryb zapisu lub jeśli w trybie zapisu istnieje jeden wątek.
Uwaga
Blokowanie nowych czytelników, gdy pisarze są w kolejce, to polityka sprawiedliwości blokady, która faworyzuje pisarzy. Obecna polityka sprawiedliwości równoważy sprawiedliwość czytelnikom i pisarzom, aby promować przepływność w najbardziej typowych scenariuszach. Przyszłe wersje platformy .NET mogą wprowadzać nowe zasady sprawiedliwości.
Wątek, który próbuje wprowadzić bloki trybu uaktualniania, jeśli istnieje już wątek w trybie uaktualniania, jeśli istnieją wątki oczekujące na wejście w tryb zapisu lub jeśli w trybie zapisu istnieje jeden wątek.
Wątek, który próbuje wprowadzić bloki trybu zapisu, jeśli istnieje wątek w dowolnym z trzech trybów.
Blokady uaktualniania i obniżania poziomu
Tryb uaktualniania jest przeznaczony dla przypadków, w których wątek zwykle odczytuje z chronionego zasobu, ale może być konieczne zapisanie go w przypadku spełnienia określonego warunku. Wątek ReaderWriterLockSlim , który został wprowadzony w trybie uaktualniania, ma dostęp do odczytu do chronionego zasobu i może uaktualnić tryb zapisu, wywołując EnterWriteLock metody lub TryEnterWriteLock . Ponieważ w trybie uaktualniania w danym momencie może istnieć tylko jeden wątek, uaktualnianie do trybu zapisu nie może zakleszczeć, gdy rekursja jest niedozwolona, co jest zasadami domyślnymi.
Ważne
Niezależnie od zasad rekursji, wątek, który początkowo wprowadził tryb odczytu, nie może uaktualnić do trybu uaktualniania lub trybu zapisu, ponieważ ten wzorzec tworzy silne prawdopodobieństwo zakleszczenia. Jeśli na przykład dwa wątki w trybie odczytu spróbują wejść w tryb zapisu, zakleszczą się. Tryb uaktualniania został zaprojektowany w celu uniknięcia takich zakleszczeń.
Jeśli istnieją inne wątki w trybie odczytu, wątek, który uaktualnia bloki. Gdy wątek jest zablokowany, inne wątki, które próbują wejść w tryb odczytu, są blokowane. Gdy wszystkie wątki wyszły z trybu odczytu, zablokowany wątek z możliwością uaktualnienia przechodzi w tryb zapisu. Jeśli istnieją inne wątki oczekujące na wejście w tryb zapisu, pozostają zablokowane, ponieważ pojedynczy wątek, który jest w trybie uaktualniania, uniemożliwia im uzyskanie wyłącznego dostępu do zasobu.
Gdy wątek w trybie uaktualniania kończy tryb zapisu, inne wątki oczekujące na wejście w tryb odczytu mogą to zrobić, chyba że istnieją wątki oczekujące na wejście w tryb zapisu. Wątek w trybie uaktualniania może uaktualnić i obniżyć na czas nieokreślony, o ile jest to jedyny wątek zapisywany w chronionym zasobie.
Ważne
Jeśli zezwolisz wielu wątkom na wprowadzanie trybu zapisu lub trybu uaktualniania, nie można zezwolić jednemu wątkowi na monopolizację trybu uaktualniania. W przeciwnym razie wątki, które próbują wejść w tryb zapisu bezpośrednio, będą blokowane na czas nieokreślony, a gdy są blokowane, inne wątki nie będą mogły wejść w tryb odczytu.
Wątek w trybie uaktualniania może obniżyć poziom do trybu odczytu, wywołując najpierw metodę EnterReadLock , a następnie wywołując metodę ExitUpgradeableReadLock . Ten wzorzec obniżania poziomu jest dozwolony dla wszystkich zasad rekursji blokady, nawet NoRecursion.
Po obniżeniu trybu odczytu wątek nie może ponownie przeprowadzić trybu uaktualniania, dopóki nie zostanie wyłączony z trybu odczytu.
Wprowadź blokadę rekursywnie
Można utworzyć obiekt ReaderWriterLockSlim obsługujący cykliczny wpis blokady przy użyciu ReaderWriterLockSlim(LockRecursionPolicy) konstruktora, który określa zasady blokowania i określa wartość LockRecursionPolicy.SupportsRecursion.
Uwaga
Stosowanie rekursji nie jest zalecane w przypadku nowego programowania, ponieważ wprowadza niepotrzebne komplikacje i sprawia, że kod jest bardziej podatny na zakleszczenia.
W przypadku elementu ReaderWriterLockSlim , który umożliwia rekursję, można powiedzieć o trybach, które może wprowadzić wątek:
Wątek w trybie odczytu może przechodzić w tryb odczytu rekursywnie, ale nie może wejść w tryb zapisu lub tryb uaktualniania. Jeśli spróbuje to zrobić, zostanie zgłoszony.LockRecursionException Wprowadzanie trybu odczytu, a następnie wprowadzanie trybu zapisu lub trybu uaktualniania jest wzorcem z silnym prawdopodobieństwem zakleszczeń, więc nie jest dozwolony. Jak wspomniano wcześniej, tryb uaktualniania jest udostępniany w przypadkach, w których konieczne jest uaktualnienie blokady.
Wątek w trybie uaktualniania może wprowadzać tryb zapisu i/lub tryb odczytu i może przechodzić dowolnego z trzech trybów cyklicznych. Jednak próba wprowadzenia bloków trybu zapisu, jeśli istnieją inne wątki w trybie odczytu.
Wątek w trybie zapisu może przechodzić w tryb odczytu i/lub tryb uaktualniania i może wejść w dowolny z trzech trybów cyklicznych.
Wątek, który nie wprowadził blokady, może wejść w dowolny tryb. Ta próba może zablokować z tych samych powodów, co próba wprowadzenia blokady niecyklicznej.
Wątek może zamknąć tryby wprowadzone w dowolnej kolejności, o ile wyjdą z każdego trybu dokładnie tyle razy, ile wszedł w ten tryb. Jeśli wątek próbuje zamknąć tryb zbyt wiele razy lub aby zamknąć tryb, który nie został wprowadzony, SynchronizationLockException jest zgłaszany.
Stany blokady
Może okazać się przydatne, aby myśleć o blokadzie pod względem jego stanów. Element ReaderWriterLockSlim może znajdować się w jednym z czterech stanów: nie został wprowadzony, odczyt, uaktualnienie i zapis.
Nie wprowadzono: w tym stanie żadne wątki nie zostały wprowadzone do blokady (lub wszystkie wątki zakończyły blokadę).
Przeczytaj: W tym stanie co najmniej jeden wątek wprowadził blokadę dostępu do odczytu do chronionego zasobu.
Uwaga
Wątek może wprowadzić blokadę w trybie odczytu przy użyciu EnterReadLock metod lub TryEnterReadLock lub przez obniżenie poziomu z trybu uaktualniania.
Uaktualnianie: w tym stanie jeden wątek wprowadził blokadę dostępu do odczytu z opcją uaktualnienia do dostępu do zapisu (czyli w trybie uaktualniania) i wprowadzono blokadę dostępu do odczytu. Nie więcej niż jeden wątek jednocześnie może wprowadzić blokadę z opcją uaktualnienia; dodatkowe wątki, które próbują wprowadzić tryb uaktualniania, są blokowane.
Zapis: w tym stanie jeden wątek wprowadził blokadę dostępu do zapisu do chronionego zasobu. Ten wątek ma wyłączne posiadanie blokady. Każdy inny wątek, który próbuje wprowadzić blokadę z jakiegokolwiek powodu, jest zablokowany.
W poniższej tabeli opisano przejścia między stanami blokad, dla blokad, które nie zezwalają na rekursję, gdy wątek t
podejmuje akcję opisaną w kolumnie po lewej stronie. W momencie wykonywania akcji t
nie ma trybu. (Specjalny przypadek, w którym t
jest w trybie uaktualniania, jest opisany w przypisach dolnych tabeli). W górnym wierszu opisano stan początkowy blokady. Komórki opisują, co się dzieje z wątkiem, i pokazują zmiany stanu blokady w nawiasach.
Przejście | Nie wprowadzono (N) | Odczyt (R) | Uaktualnianie (U) | Zapis (W) |
---|---|---|---|---|
t wprowadza tryb odczytu |
t enters (R). |
t bloki, jeśli wątki oczekują na tryb zapisu; t w przeciwnym razie wprowadza. |
t bloki, jeśli wątki oczekują na tryb zapisu; t w przeciwnym razie wprowadza.1 |
t Bloki. |
t wprowadza tryb uaktualniania |
t enters (U). |
t blokuje, jeśli wątki oczekują na tryb zapisu lub tryb uaktualniania; t w przeciwnym razie wprowadza wartość (U). |
t Bloki. |
t Bloki. |
t wprowadza tryb zapisu |
t enters (W). |
t Bloki. |
t Bloki.2 |
t Bloki. |
1 Jeśli t
rozpoczyna się w trybie uaktualniania, wchodzi w tryb odczytu. Ta akcja nigdy nie blokuje. Stan blokady nie zmienia się. (Wątek może następnie wykonać obniżenie poziomu do trybu odczytu, zamykając tryb uaktualniania).
2 Jeśli t
rozpoczyna się w trybie uaktualniania, blokuje, czy istnieją wątki w trybie odczytu. W przeciwnym razie uaktualnia się do trybu zapisu. Stan blokady zmienia się na Zapis (W). Jeśli t
bloki, ponieważ istnieją wątki w trybie odczytu, wchodzi w tryb zapisu, gdy tylko ostatni wątek zakończy tryb odczytu, nawet jeśli wątek czeka na wejście w tryb zapisu.
Gdy wystąpi zmiana stanu, ponieważ wątek zamyka blokadę, następny wątek do przebudzenia jest wybierany w następujący sposób:
- Po pierwsze, wątek, który czeka na tryb zapisu i jest już w trybie uaktualniania (może istnieć co najwyżej jeden taki wątek).
- W przeciwnym razie wątek, który czeka na tryb zapisu.
- W przeciwnym razie wątek, który czeka na tryb uaktualniania.
- W przeciwnym razie wszystkie wątki oczekujące na tryb odczytu.
Kolejnym stanem blokady jest zawsze zapis (W) w dwóch pierwszych przypadkach i uaktualnienie (U) w trzecim przypadku, niezależnie od stanu blokady, gdy wątek wyjścia wyzwolił zmianę stanu. W ostatnim przypadku stan blokady to Uaktualnienie (U), jeśli istnieje wątek w trybie uaktualniania po zmianie stanu, a odczyt (R) w przeciwnym razie, niezależnie od poprzedniego stanu.
Przykłady
W poniższym przykładzie przedstawiono prostą zsynchronizowaną pamięć podręczną zawierającą ciągi z kluczami całkowitymi. Wystąpienie ReaderWriterLockSlim klasy służy do synchronizowania dostępu do Dictionary<TKey,TValue> obiektu, który służy jako wewnętrzna pamięć podręczna.
Przykład zawiera proste metody dodawania do pamięci podręcznej, usuwania z pamięci podręcznej i odczytywania z pamięci podręcznej. Aby zademonstrować przekroczenia limitu czasu, przykład zawiera metodę dodaną do pamięci podręcznej tylko wtedy, gdy może to zrobić w określonym przedziale czasu.
Aby zademonstrować tryb uaktualniania, przykład zawiera metodę, która pobiera wartość skojarzona z kluczem i porównuje ją z nową wartością. Jeśli wartość jest niezmieniona, metoda zwraca stan wskazujący brak zmian. Jeśli dla klucza nie zostanie znaleziona żadna wartość, zostanie wstawiona para klucz/wartość. Jeśli wartość została zmieniona, zostanie ona zaktualizowana. Tryb uaktualniania umożliwia wątkowi uaktualnienie z dostępu do odczytu do zapisu zgodnie z potrzebami bez ryzyka zakleszczenia.
Przykład zawiera zagnieżdżone wyliczenie, które określa zwracane wartości dla metody, która demonstruje tryb uaktualniania.
W przykładzie użyto konstruktora bez parametrów do utworzenia blokady, więc rekursja nie jest dozwolona. Programowanie jest ReaderWriterLockSlim prostsze i mniej podatne na błędy, gdy blokada nie zezwala na rekursję.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
public class SynchronizedCache
{
private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
private Dictionary<int, string> innerCache = new Dictionary<int, string>();
public int Count
{ get { return innerCache.Count; } }
public string Read(int key)
{
cacheLock.EnterReadLock();
try
{
return innerCache[key];
}
finally
{
cacheLock.ExitReadLock();
}
}
public void Add(int key, string value)
{
cacheLock.EnterWriteLock();
try
{
innerCache.Add(key, value);
}
finally
{
cacheLock.ExitWriteLock();
}
}
public bool AddWithTimeout(int key, string value, int timeout)
{
if (cacheLock.TryEnterWriteLock(timeout))
{
try
{
innerCache.Add(key, value);
}
finally
{
cacheLock.ExitWriteLock();
}
return true;
}
else
{
return false;
}
}
public AddOrUpdateStatus AddOrUpdate(int key, string value)
{
cacheLock.EnterUpgradeableReadLock();
try
{
string result = null;
if (innerCache.TryGetValue(key, out result))
{
if (result == value)
{
return AddOrUpdateStatus.Unchanged;
}
else
{
cacheLock.EnterWriteLock();
try
{
innerCache[key] = value;
}
finally
{
cacheLock.ExitWriteLock();
}
return AddOrUpdateStatus.Updated;
}
}
else
{
cacheLock.EnterWriteLock();
try
{
innerCache.Add(key, value);
}
finally
{
cacheLock.ExitWriteLock();
}
return AddOrUpdateStatus.Added;
}
}
finally
{
cacheLock.ExitUpgradeableReadLock();
}
}
public void Delete(int key)
{
cacheLock.EnterWriteLock();
try
{
innerCache.Remove(key);
}
finally
{
cacheLock.ExitWriteLock();
}
}
public enum AddOrUpdateStatus
{
Added,
Updated,
Unchanged
};
~SynchronizedCache()
{
if (cacheLock != null) cacheLock.Dispose();
}
}
Public Class SynchronizedCache
Private cacheLock As New ReaderWriterLockSlim()
Private innerCache As New Dictionary(Of Integer, String)
Public ReadOnly Property Count As Integer
Get
Return innerCache.Count
End Get
End Property
Public Function Read(ByVal key As Integer) As String
cacheLock.EnterReadLock()
Try
Return innerCache(key)
Finally
cacheLock.ExitReadLock()
End Try
End Function
Public Sub Add(ByVal key As Integer, ByVal value As String)
cacheLock.EnterWriteLock()
Try
innerCache.Add(key, value)
Finally
cacheLock.ExitWriteLock()
End Try
End Sub
Public Function AddWithTimeout(ByVal key As Integer, ByVal value As String, _
ByVal timeout As Integer) As Boolean
If cacheLock.TryEnterWriteLock(timeout) Then
Try
innerCache.Add(key, value)
Finally
cacheLock.ExitWriteLock()
End Try
Return True
Else
Return False
End If
End Function
Public Function AddOrUpdate(ByVal key As Integer, _
ByVal value As String) As AddOrUpdateStatus
cacheLock.EnterUpgradeableReadLock()
Try
Dim result As String = Nothing
If innerCache.TryGetValue(key, result) Then
If result = value Then
Return AddOrUpdateStatus.Unchanged
Else
cacheLock.EnterWriteLock()
Try
innerCache.Item(key) = value
Finally
cacheLock.ExitWriteLock()
End Try
Return AddOrUpdateStatus.Updated
End If
Else
cacheLock.EnterWriteLock()
Try
innerCache.Add(key, value)
Finally
cacheLock.ExitWriteLock()
End Try
Return AddOrUpdateStatus.Added
End If
Finally
cacheLock.ExitUpgradeableReadLock()
End Try
End Function
Public Sub Delete(ByVal key As Integer)
cacheLock.EnterWriteLock()
Try
innerCache.Remove(key)
Finally
cacheLock.ExitWriteLock()
End Try
End Sub
Public Enum AddOrUpdateStatus
Added
Updated
Unchanged
End Enum
Protected Overrides Sub Finalize()
If cacheLock IsNot Nothing Then cacheLock.Dispose()
End Sub
End Class
Poniższy kod używa SynchronizedCache
następnie obiektu do przechowywania słownika nazw warzyw. Tworzy trzy zadania. Pierwszy zapisuje nazwy warzyw przechowywanych w tablicy do SynchronizedCache
wystąpienia. Drugie i trzecie zadanie wyświetla nazwy warzyw, pierwszy w kolejności rosnącej (od niskiego indeksu do wysokiego indeksu), drugi w kolejności malejącej. Ostatnie zadanie wyszukuje ciąg "ogórk" i, gdy go znajdzie, wywołuje EnterUpgradeableReadLock metodę, aby zastąpić ciąg "zielony fasola".
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
public class Example
{
public static void Main()
{
var sc = new SynchronizedCache();
var tasks = new List<Task>();
int itemsWritten = 0;
// Execute a writer.
tasks.Add(Task.Run( () => { String[] vegetables = { "broccoli", "cauliflower",
"carrot", "sorrel", "baby turnip",
"beet", "brussel sprout",
"cabbage", "plantain",
"spinach", "grape leaves",
"lime leaves", "corn",
"radish", "cucumber",
"raddichio", "lima beans" };
for (int ctr = 1; ctr <= vegetables.Length; ctr++)
sc.Add(ctr, vegetables[ctr - 1]);
itemsWritten = vegetables.Length;
Console.WriteLine("Task {0} wrote {1} items\n",
Task.CurrentId, itemsWritten);
} ));
// Execute two readers, one to read from first to last and the second from last to first.
for (int ctr = 0; ctr <= 1; ctr++) {
bool desc = ctr == 1;
tasks.Add(Task.Run( () => { int start, last, step;
int items;
do {
String output = String.Empty;
items = sc.Count;
if (! desc) {
start = 1;
step = 1;
last = items;
}
else {
start = items;
step = -1;
last = 1;
}
for (int index = start; desc ? index >= last : index <= last; index += step)
output += String.Format("[{0}] ", sc.Read(index));
Console.WriteLine("Task {0} read {1} items: {2}\n",
Task.CurrentId, items, output);
} while (items < itemsWritten | itemsWritten == 0);
} ));
}
// Execute a red/update task.
tasks.Add(Task.Run( () => { Thread.Sleep(100);
for (int ctr = 1; ctr <= sc.Count; ctr++) {
String value = sc.Read(ctr);
if (value == "cucumber")
if (sc.AddOrUpdate(ctr, "green bean") != SynchronizedCache.AddOrUpdateStatus.Unchanged)
Console.WriteLine("Changed 'cucumber' to 'green bean'");
}
} ));
// Wait for all three tasks to complete.
Task.WaitAll(tasks.ToArray());
// Display the final contents of the cache.
Console.WriteLine();
Console.WriteLine("Values in synchronized cache: ");
for (int ctr = 1; ctr <= sc.Count; ctr++)
Console.WriteLine(" {0}: {1}", ctr, sc.Read(ctr));
}
}
// The example displays the following output:
// Task 1 read 0 items:
//
// Task 3 wrote 17 items
//
//
// Task 1 read 17 items: [broccoli] [cauliflower] [carrot] [sorrel] [baby turnip] [
// beet] [brussel sprout] [cabbage] [plantain] [spinach] [grape leaves] [lime leave
// s] [corn] [radish] [cucumber] [raddichio] [lima beans]
//
// Task 2 read 0 items:
//
// Task 2 read 17 items: [lima beans] [raddichio] [cucumber] [radish] [corn] [lime
// leaves] [grape leaves] [spinach] [plantain] [cabbage] [brussel sprout] [beet] [b
// aby turnip] [sorrel] [carrot] [cauliflower] [broccoli]
//
// Changed 'cucumber' to 'green bean'
//
// Values in synchronized cache:
// 1: broccoli
// 2: cauliflower
// 3: carrot
// 4: sorrel
// 5: baby turnip
// 6: beet
// 7: brussel sprout
// 8: cabbage
// 9: plantain
// 10: spinach
// 11: grape leaves
// 12: lime leaves
// 13: corn
// 14: radish
// 15: green bean
// 16: raddichio
// 17: lima beans
Public Module Example
Public Sub Main()
Dim sc As New SynchronizedCache()
Dim tasks As New List(Of Task)
Dim itemsWritten As Integer
' Execute a writer.
tasks.Add(Task.Run( Sub()
Dim vegetables() As String = { "broccoli", "cauliflower",
"carrot", "sorrel", "baby turnip",
"beet", "brussel sprout",
"cabbage", "plantain",
"spinach", "grape leaves",
"lime leaves", "corn",
"radish", "cucumber",
"raddichio", "lima beans" }
For ctr As Integer = 1 to vegetables.Length
sc.Add(ctr, vegetables(ctr - 1))
Next
itemsWritten = vegetables.Length
Console.WriteLine("Task {0} wrote {1} items{2}",
Task.CurrentId, itemsWritten, vbCrLf)
End Sub))
' Execute two readers, one to read from first to last and the second from last to first.
For ctr As Integer = 0 To 1
Dim flag As Integer = ctr
tasks.Add(Task.Run( Sub()
Dim start, last, stp As Integer
Dim items As Integer
Do
Dim output As String = String.Empty
items = sc.Count
If flag = 0 Then
start = 1 : stp = 1 : last = items
Else
start = items : stp = -1 : last = 1
End If
For index As Integer = start To last Step stp
output += String.Format("[{0}] ", sc.Read(index))
Next
Console.WriteLine("Task {0} read {1} items: {2}{3}",
Task.CurrentId, items, output,
vbCrLf)
Loop While items < itemsWritten Or itemsWritten = 0
End Sub))
Next
' Execute a red/update task.
tasks.Add(Task.Run( Sub()
For ctr As Integer = 1 To sc.Count
Dim value As String = sc.Read(ctr)
If value = "cucumber" Then
If sc.AddOrUpdate(ctr, "green bean") <> SynchronizedCache.AddOrUpdateStatus.Unchanged Then
Console.WriteLine("Changed 'cucumber' to 'green bean'")
End If
End If
Next
End Sub ))
' Wait for all three tasks to complete.
Task.WaitAll(tasks.ToArray())
' Display the final contents of the cache.
Console.WriteLine()
Console.WriteLine("Values in synchronized cache: ")
For ctr As Integer = 1 To sc.Count
Console.WriteLine(" {0}: {1}", ctr, sc.Read(ctr))
Next
End Sub
End Module
' The example displays output like the following:
' Task 1 read 0 items:
'
' Task 3 wrote 17 items
'
' Task 1 read 17 items: [broccoli] [cauliflower] [carrot] [sorrel] [baby turnip] [
' beet] [brussel sprout] [cabbage] [plantain] [spinach] [grape leaves] [lime leave
' s] [corn] [radish] [cucumber] [raddichio] [lima beans]
'
' Task 2 read 0 items:
'
' Task 2 read 17 items: [lima beans] [raddichio] [cucumber] [radish] [corn] [lime
' leaves] [grape leaves] [spinach] [plantain] [cabbage] [brussel sprout] [beet] [b
' aby turnip] [sorrel] [carrot] [cauliflower] [broccoli]
'
' Changed 'cucumber' to 'green bean'
'
' Values in synchronized cache:
' 1: broccoli
' 2: cauliflower
' 3: carrot
' 4: sorrel
' 5: baby turnip
' 6: beet
' 7: brussel sprout
' 8: cabbage
' 9: plantain
' 10: spinach
' 11: grape leaves
' 12: lime leaves
' 13: corn
' 14: radish
' 15: green bean
' 16: raddichio
' 17: lima beans