Classe System.Threading.ReaderWriterLockSlim
Cet article vous offre des remarques complémentaires à la documentation de référence pour cette API.
Permet ReaderWriterLockSlim de protéger une ressource lue par plusieurs threads et écrite par un thread à la fois. ReaderWriterLockSlim permet à plusieurs threads d’être en mode lecture, permet à un thread d’être en mode écriture avec la propriété exclusive du verrou et permet à un thread disposant d’un accès en lecture pouvant être en mode lecture mis à niveau, à partir duquel le thread peut effectuer une mise à niveau vers le mode d’écriture sans avoir à renoncer à son accès en lecture à la ressource.
Remarque
- ReaderWriterLockSlim est similaire à ReaderWriterLock, mais a des règles simplifiées pour la récursivité ainsi que la mise à niveau et la rétrogradation de l’état de verrou. ReaderWriterLockSlim évite de nombreux cas d’interblocage potentiel. En outre, les performances de ReaderWriterLockSlim sont considérablement meilleures que celles de ReaderWriterLock. ReaderWriterLockSlim est recommandé pour tout nouveau développement.
- ReaderWriterLockSlim n’est pas safe thread-abort. Vous ne devez pas l’utiliser dans un environnement où les threads qui y accèdent peuvent être abandonnés, tels que .NET Framework. Si vous utilisez .NET Core ou .NET 5+, il doit être correct. Abort n’est pas pris en charge dans .NET Core et est obsolète dans .NET 5 et versions ultérieures.
Par défaut, de nouvelles instances sont ReaderWriterLockSlim créées avec l’indicateur et n’autorisent pas la LockRecursionPolicy.NoRecursion récursivité. Cette stratégie par défaut est recommandée pour tous les nouveaux développements, car la récursivité introduit des complications inutiles et rend votre code plus susceptible d’interblocages. Pour simplifier la migration à partir de projets existants qui utilisent Monitor ou ReaderWriterLock, vous pouvez utiliser l’indicateur LockRecursionPolicy.SupportsRecursion pour créer des instances de ReaderWriterLockSlim ce qui autorise la récursivité.
Un thread peut entrer le verrou en trois modes : mode lecture, mode écriture et mode lecture pouvant être mis à niveau. (Dans le reste de cette rubrique, le « mode de lecture pouvant être mis à niveau » est appelé « mode pouvant être mis à niveau », et l’expression « mode entrée x
» est utilisée en préférence pour l’expression plus longue « entrer le verrou en x
mode ».
Quelle que soit la stratégie de récursivité, un seul thread peut être en mode écriture à tout moment. Lorsqu’un thread est en mode écriture, aucun autre thread ne peut entrer dans le verrou en tout mode. Un seul thread peut être en mode pouvant être mis à niveau à tout moment. Un nombre quelconque de threads peut être en mode lecture, et il peut y avoir un thread en mode mise à niveau alors que d’autres threads sont en mode lecture.
Important
Ce type implémente l'interface IDisposable. Une fois que vous avez fini d’utiliser le type, vous devez le supprimer directement ou indirectement. Pour supprimer directement le type Dispose, appelez sa méthode dans un bloc try
/catch
. Pour la supprimer indirectement, utilisez une construction de langage telle que using
(dans C#) ou Using
(dans Visual Basic). Pour plus d’informations, consultez la section « Utilisation d’un objet qui implémente IDisposable » dans la rubrique de l’interface IDisposable.
ReaderWriterLockSlim a une affinité de thread managé ; autrement dit, chaque Thread objet doit effectuer ses propres appels de méthode pour entrer et quitter les modes de verrouillage. Aucun thread ne peut modifier le mode d’un autre thread.
Si une ReaderWriterLockSlim récursivité n’autorise pas la récursivité, un thread qui tente d’entrer dans le verrou peut bloquer pour plusieurs raisons :
Thread qui tente d’entrer des blocs en mode lecture s’il existe des threads en attente d’entrer en mode écriture ou s’il existe un thread unique en mode écriture.
Remarque
Le blocage de nouveaux lecteurs lorsque les enregistreurs sont mis en file d’attente est une stratégie d’équité des verrous qui favorise les enregistreurs. La stratégie d’équité actuelle équilibre l’équité avec les lecteurs et les rédacteurs, afin de promouvoir le débit dans les scénarios les plus courants. Les futures versions de .NET peuvent introduire de nouvelles stratégies d’équité.
Thread qui tente d’entrer des blocs de mode pouvant être mis à niveau s’il existe déjà un thread en mode mise à niveau, s’il existe des threads qui attendent d’entrer en mode écriture ou s’il existe un thread unique en mode écriture.
Thread qui tente d’entrer des blocs de mode d’écriture s’il existe un thread dans l’un des trois modes.
Mettre à niveau et rétrograder les verrous
Le mode pouvant être mis à niveau est destiné aux cas où un thread lit généralement à partir de la ressource protégée, mais il peut être nécessaire d’y écrire si une condition est remplie. Un thread qui a entré un mode pouvant être mis à niveau a un ReaderWriterLockSlim accès en lecture à la ressource protégée et peut effectuer une mise à niveau vers le mode d’écriture en appelant le ou TryEnterWriteLock les EnterWriteLock méthodes. Étant donné qu’il ne peut y avoir qu’un seul thread en mode pouvant être mis à niveau à la fois, la mise à niveau vers le mode d’écriture ne peut pas se bloquer lorsque la récursivité n’est pas autorisée, qui est la stratégie par défaut.
Important
Quelle que soit la stratégie de récursivité, un thread entré initialement en mode lecture n’est pas autorisé à effectuer une mise à niveau vers un mode pouvant être mis à niveau ou un mode d’écriture, car ce modèle crée une probabilité forte d’interblocages. Par exemple, si deux threads en mode lecture essaient d’entrer en mode écriture, ils sont bloqués. Le mode pouvant être mis à niveau est conçu pour éviter ces interblocages.
S’il existe d’autres threads en mode lecture, le thread qui met à niveau les blocs. Pendant que le thread est bloqué, d’autres threads qui tentent d’entrer en mode lecture sont bloqués. Lorsque tous les threads ont quitté le mode lecture, le thread pouvant être mis à niveau bloqué entre en mode écriture. S’il existe d’autres threads qui attendent d’entrer en mode écriture, ils restent bloqués, car le thread unique en mode pouvant être mis à niveau les empêche d’accéder exclusivement à la ressource.
Lorsque le thread en mode mise à niveau quitte le mode d’écriture, d’autres threads qui attendent d’entrer en mode lecture peuvent le faire, sauf s’il existe des threads qui attendent d’entrer en mode écriture. Le thread en mode mise à niveau peut mettre à niveau et rétrograder indéfiniment, tant qu’il s’agit du seul thread qui écrit dans la ressource protégée.
Important
Si vous autorisez plusieurs threads à entrer en mode écriture ou en mode mise à niveau, vous ne devez pas autoriser un thread à monopoliser le mode pouvant être mis à niveau. Dans le cas contraire, les threads qui tentent d’entrer directement en mode écriture seront bloqués indéfiniment et, pendant qu’ils sont bloqués, d’autres threads ne pourront pas entrer en mode lecture.
Un thread en mode pouvant être mis à niveau peut passer en mode lecture en appelant d’abord la EnterReadLock méthode, puis en appelant la ExitUpgradeableReadLock méthode. Ce modèle de rétrogradation est autorisé pour toutes les stratégies de récursivité de verrou, même NoRecursion.
Après la rétrogradation en mode lecture, un thread ne peut pas reentérer le mode pouvant être mis à niveau tant qu’il n’a pas quitté le mode lecture.
Entrez le verrou de manière récursive
Vous pouvez créer un ReaderWriterLockSlim qui prend en charge l’entrée de verrou récursif à l’aide du ReaderWriterLockSlim(LockRecursionPolicy) constructeur qui spécifie la stratégie de verrouillage et en spécifiant LockRecursionPolicy.SupportsRecursion.
Remarque
L’utilisation de la récursivité n’est pas recommandée pour le nouveau développement, car elle introduit des complications inutiles et rend votre code plus susceptible d’interblocages.
Pour une ReaderWriterLockSlim opération qui autorise la récursivité, vous pouvez indiquer ce qui suit sur les modes qu’un thread peut entrer :
Un thread en mode lecture peut entrer en mode lecture récursivement, mais ne peut pas entrer en mode écriture ou en mode mise à niveau. S’il tente de le faire, une LockRecursionException est levée. Entrer en mode lecture, puis entrer en mode écriture ou mise à niveau est un modèle avec une probabilité forte d’interblocages, de sorte qu’il n’est pas autorisé. Comme indiqué précédemment, le mode pouvant être mis à niveau est fourni dans les cas où il est nécessaire de mettre à niveau un verrou.
Un thread en mode pouvant être mis à niveau peut entrer en mode écriture et/ou en mode lecture, et peut entrer l’un des trois modes de manière récursive. Toutefois, une tentative d’entrer des blocs en mode écriture s’il existe d’autres threads en mode lecture.
Un thread en mode écriture peut entrer en mode lecture et/ou en mode mise à niveau, et peut entrer l’un des trois modes de manière récursive.
Un thread qui n’a pas entré le verrou peut entrer n’importe quel mode. Cette tentative peut bloquer pour les mêmes raisons qu’une tentative d’entrée d’un verrou non récursif.
Un thread peut quitter les modes qu’il a entrés dans n’importe quel ordre, tant qu’il quitte chaque mode exactement autant de fois qu’il est entré dans ce mode. Si un thread tente de quitter un mode trop de fois ou de quitter un mode qu’il n’a pas entré, il SynchronizationLockException est levée.
États de verrouillage
Vous trouverez peut-être utile de penser au verrou en termes de ses états. Un ReaderWriterLockSlim peut être dans l’un des quatre états suivants : non entré, lu, mis à niveau et écriture.
Non entré : dans cet état, aucun thread n’a entré le verrou (ou tous les threads ont quitté le verrou).
Lecture : dans cet état, un ou plusieurs threads ont entré le verrou pour l’accès en lecture à la ressource protégée.
Remarque
Un thread peut entrer le verrou en mode lecture à l’aide des EnterReadLock méthodes ou TryEnterReadLock des méthodes, ou en rétrogradant à partir du mode pouvant être mis à niveau.
Mise à niveau : dans cet état, un thread a entré le verrou pour l’accès en lecture avec l’option de mise à niveau pour l’accès en écriture (autrement dit, en mode pouvant être mis à niveau) et zéro ou plusieurs threads ont entré le verrou pour l’accès en lecture. Plus d’un thread à la fois ne peut entrer dans le verrou avec l’option de mise à niveau ; d’autres threads qui tentent d’entrer en mode pouvant être mis à niveau sont bloqués.
Écriture : dans cet état, un thread a entré le verrou pour l’accès en écriture à la ressource protégée. Ce thread a la possession exclusive du verrou. Tout autre thread qui tente d’entrer le verrou pour une raison quelconque est bloqué.
Le tableau suivant décrit les transitions entre les états de verrou, pour les verrous qui n’autorisent pas la récursivité, lorsqu’un thread t
effectue l’action décrite dans la colonne la plus à gauche. Au moment où elle prend l’action, t
n’a aucun mode. (Le cas particulier où t
se trouve le mode pouvant être mis à niveau est décrit dans les notes de bas de page du tableau.) La ligne supérieure décrit l’état de départ du verrou. Les cellules décrivent ce qui arrive au thread et affichent les modifications apportées à l’état de verrouillage entre parenthèses.
Transition | Non entré (N) | Lecture (R) | Mise à niveau (U) | Écriture (W) |
---|---|---|---|---|
t entre en mode lecture |
t entre (R). |
t bloque si les threads attendent le mode d’écriture ; sinon, t entre. |
t bloque si les threads attendent le mode d’écriture ; sinon, t entre.1 |
t Blocs. |
t entre en mode pouvant être mis à niveau |
t entre (U). |
t bloque si les threads attendent le mode d’écriture ou le mode de mise à niveau ; sinon, t entre (U). |
t Blocs. |
t Blocs. |
t entre en mode d’écriture |
t entre (W). |
t Blocs. |
t Blocs.2 |
t Blocs. |
1 Si t
elle démarre en mode mise à niveau, elle entre en mode lecture. Cette action ne bloque jamais. L’état du verrou ne change pas. (Le thread peut ensuite effectuer une rétrogradation en mode lecture en quittant le mode pouvant être mis à niveau.)
2 Si t
elle démarre en mode mise à niveau, elle bloque s’il existe des threads en mode lecture. Sinon, il est mis à niveau vers le mode d’écriture. L’état de verrouillage change en écriture (W). Si t
des blocs sont bloqués, car il existe des threads en mode lecture, il entre en mode écriture dès que le dernier thread quitte le mode lecture, même s’il y a des threads en attente d’entrer en mode écriture.
Lorsqu’une modification d’état se produit parce qu’un thread quitte le verrou, le thread suivant à réveiller est sélectionné comme suit :
- Tout d’abord, un thread qui attend le mode d’écriture et qui est déjà en mode mise à niveau (il peut y avoir au maximum un thread de ce type).
- En cas d’échec, un thread qui attend le mode d’écriture.
- En cas d’échec, un thread qui attend le mode pouvant être mis à niveau.
- En cas d’échec, tous les threads qui attendent le mode lecture.
L’état suivant du verrou est toujours Écriture (W) dans les deux premiers cas et Mise à niveau (U) dans le troisième cas, quel que soit l’état du verrou lorsque le thread de sortie a déclenché la modification de l’état. Dans le dernier cas, l’état du verrou est Mise à niveau (U) s’il existe un thread en mode pouvant être mis à niveau après la modification de l’état et en lecture (R) dans le cas contraire, quel que soit l’état précédent.
Exemples
L’exemple suivant montre un cache synchronisé simple qui contient des chaînes avec des clés entières. Une instance de ReaderWriterLockSlim est utilisée pour synchroniser l’accès au Dictionary<TKey,TValue> cache interne.
L’exemple inclut des méthodes simples à ajouter au cache, supprimer du cache et lire à partir du cache. Pour illustrer les délais d’attente, l’exemple inclut une méthode qui ajoute au cache uniquement s’il peut le faire dans un délai d’attente spécifié.
Pour illustrer le mode pouvant être mis à niveau, l’exemple inclut une méthode qui récupère la valeur associée à une clé et la compare à une nouvelle valeur. Si la valeur n’est pas modifiée, la méthode retourne un état indiquant qu’aucune modification n’est apportée. Si aucune valeur n’est trouvée pour la clé, la paire clé/valeur est insérée. Si la valeur a changé, elle est mise à jour. Le mode pouvant être mis à niveau permet au thread de mettre à niveau à partir de l’accès en lecture à l’accès en écriture si nécessaire, sans risque d’interblocages.
L’exemple inclut une énumération imbriquée qui spécifie les valeurs de retour de la méthode qui illustre le mode pouvant être mis à niveau.
L’exemple utilise le constructeur sans paramètre pour créer le verrou, de sorte que la récursivité n’est pas autorisée. La programmation est ReaderWriterLockSlim plus simple et moins sujette à une erreur lorsque le verrou n’autorise pas la récursivité.
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
Le code suivant utilise ensuite l’objet SynchronizedCache
pour stocker un dictionnaire de noms de légumes. Il crée trois tâches. Le premier écrit les noms des légumes stockés dans un tableau dans une SynchronizedCache
instance. La deuxième et la troisième tâche affichent les noms des légumes, le premier dans l’ordre croissant (de l’index faible à l’index élevé), le deuxième dans l’ordre décroissant. La tâche finale recherche la chaîne « concombre » et, lorsqu’elle la trouve, appelle la EnterUpgradeableReadLock méthode pour remplacer la chaîne « bean vert ».
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