Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Cet article fournit des remarques supplémentaires à la documentation de référence de 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 il a simplifié les règles de récursivité et de mise à niveau et de rétrogradation de l’état de verrouillage. ReaderWriterLockSlim évite de nombreux cas d’interblocage potentiel. En outre, la performance de ReaderWriterLockSlim est nettement meilleure que celle de ReaderWriterLock. ReaderWriterLockSlim est recommandé pour tout nouveau développement.
- ReaderWriterLockSlim n’est pas protégé contre les arrêts de threads. 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+, cela devrait aller. 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 de ReaderWriterLockSlim sont créées avec le drapeau LockRecursionPolicy.NoRecursion et n’autorisent pas la 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 « entrer en mode x
» est préférée à l'expression plus longue « entrer le verrou en mode x
».)
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.
Importante
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, appelez sa Dispose méthode dans un try
/catch
bloc. Pour la supprimer indirectement, utilisez une construction de langage telle que using
(en C#) ou Using
(en Visual Basic). Pour plus d’informations, consultez la section « Utilisation d’un objet implémentant IDisposable » dans la rubrique d’interface IDisposable .
ReaderWriterLockSlim a une affinité de threads managée, c'est-à-dire que chaque objet Thread 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 un ReaderWriterLockSlim n'autorise pas la récursion, un thread qui tente d'entrer dans le verrou peut bloquer pour plusieurs raisons :
Un thread qui tente d’entrer en mode lecture se bloque s’il existe des threads en attente d’entrer en mode écriture ou s’il existe un seul thread en mode écriture.
Remarque
Le blocage de nouveaux lecteurs lorsque les auteurs sont mis en file d'attente est une politique d'équité des verrous qui favorise les écrivains. La stratégie d'équité actuelle assure un équilibre entre l'équité envers les lecteurs et les rédacteurs, afin de promouvoir l'efficacité dans les scénarios les plus courants. Les futures versions de .NET peuvent introduire de nouvelles stratégies d’équité.
Un thread qui tente d’entrer en mode évolvable se bloque s'il y a déjà un thread dans ce mode, s'il y a des threads en attente pour entrer en mode écriture, ou s'il y a un seul thread en mode écriture.
Un fil qui tente d'entrer en mode d'écriture se bloque s'il existe un fil 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 est entré dans un ReaderWriterLockSlim dans un mode pouvant être mis à niveau dispose d'un accès en lecture à la ressource protégée et peut effectuer une mise à niveau vers le mode d’écriture en appelant les méthodes EnterWriteLock ou TryEnterWriteLock. É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.
Importante
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 évolutif peut effectuer des mises à niveau et des rétrogradations sans limite, tant qu'il reste le seul thread à écrire dans la ressource protégée.
Importante
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 qui autorise la récursivité, on peut dire ce qui suit sur les modes dans lesquels 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 de passer en mode écriture est bloquée 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 activé, une 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 saisi, lu, mise à jour, 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 dans le verrou en mode lecture à l’aide des méthodes EnterReadLock ou TryEnterReadLock, ou en rétrogradant du mode améliorable.
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 en mode pouvant être mis à niveau est décrit dans les notes de bas de page du tableau.) La première ligne décrit l'état initial 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 dans (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 bloque. |
t entre en mode pouvant être mis à niveau |
t entre dans (U). |
t bloque si les threads attendent le mode d’écriture ou le mode de mise à niveau ; sinon, t entre dans (U). |
t bloque. |
t bloque. |
t entre en mode d’écriture |
t entre dans (W). |
t bloque. |
t bloque.2 |
t bloque. |
1 Si t
démarre en mode de mise à niveau, il 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
démarre en mode d'upgrade, elle bloque lorsqu'il y a des threads en mode lecture. Sinon, il passe en mode écriture. L'état de verrouillage passe à Écriture (E). Lorsque t
doit attendre parce qu'il y a des threads en mode lecture, il passe en mode écriture dès que le dernier thread sort du 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 en attente du mode d’écriture.
- En cas d’échec, un thread en attente du mode de mise à 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 de mise à niveau après la modification de l'état et Lecture (R) sinon, 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 de ReaderWriterLockSlim est plus simple et moins sujette aux erreurs 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 {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