Classe System.Threading.Monitor
Cet article vous offre des remarques complémentaires à la documentation de référence pour cette API.
La Monitor classe vous permet de synchroniser l’accès à une région de code en prenant et en libérant un verrou sur un objet particulier en appelant les méthodes et Monitor.Exit les Monitor.EnterméthodesMonitor.TryEnter. Les verrous d’objet permettent de restreindre l’accès à un bloc de code, communément appelé section critique. Alors qu’un thread possède le verrou d’un objet, aucun autre thread ne peut acquérir ce verrou. Vous pouvez également utiliser la Monitor classe pour vous assurer qu’aucun autre thread n’est autorisé à accéder à une section du code d’application exécutée par le propriétaire du verrou, sauf si l’autre thread exécute le code à l’aide d’un autre objet verrouillé. Étant donné que la classe Monitor a une affinité de thread, le thread qui a acquis un verrou doit libérer le verrou en appelant la méthode Monitor.Exit.
Vue d’ensemble
Monitor présente les caractéristiques suivantes :
- Il est associé à un objet à la demande.
- Il n’est pas lié, ce qui signifie qu’il peut être appelé directement à partir de n’importe quel contexte.
- Impossible de créer une instance de la Monitor classe ; les méthodes de la Monitor classe sont toutes statiques. Chaque méthode est passée à l’objet synchronisé qui contrôle l’accès à la section critique.
Remarque
Utilisez la Monitor classe pour verrouiller des objets autres que des chaînes (autrement dit, des types référence autres que ), et non des Stringtypes valeur. Pour plus d’informations, consultez les surcharges de la méthode et de la section de l’objet Enter verrou plus loin dans cet article.
Le tableau suivant décrit les actions qui peuvent être effectuées par des threads qui accèdent aux objets synchronisés :
Action | Description |
---|---|
Enter, TryEnter | Acquiert un verrou pour un objet. Cette action marque également le début d’une section critique. Aucun autre thread ne peut entrer dans la section critique, sauf s’il exécute les instructions de la section critique à l’aide d’un autre objet verrouillé. |
Wait | Libère le verrou sur un objet pour permettre à d’autres threads de verrouiller et d’accéder à l’objet. Le thread appelant attend pendant qu’un autre thread accède à l’objet. Les signaux d’impulsion sont utilisés pour avertir les threads en attente des modifications apportées à l’état d’un objet. |
Pulse (signal), PulseAll | Envoie un signal à un ou plusieurs threads en attente. Le signal avertit un thread en attente que l’état de l’objet verrouillé a changé et que le propriétaire du verrou est prêt à libérer le verrou. Le thread en attente est placé dans la file d’attente prête de l’objet afin qu’il puisse éventuellement recevoir le verrou de l’objet. Une fois que le thread a le verrou, il peut case activée le nouvel état de l’objet pour voir si l’état requis a été atteint. |
Exit | Libère le verrou sur un objet. Cette action marque également la fin d’une section critique protégée par l’objet verrouillé. |
Il existe deux ensembles de surcharges pour les méthodes et TryEnter les Enter méthodes. Un ensemble de surcharges a un ref
paramètre (en C#) ou ByRef
(en Visual Basic) Boolean défini atomiquement true
si le verrou est acquis, même si une exception est levée lors de l’acquisition du verrou. Utilisez ces surcharges s’il est essentiel de libérer le verrou dans tous les cas, même si les ressources protégées par le verrou peuvent ne pas être dans un état cohérent.
Objet de verrouillage
La classe Monitor se compose de static
méthodes (Shared
en Visual Basic) qui fonctionnent sur un objet qui contrôle l’accès à la section critique. Les informations suivantes sont conservées pour chaque objet synchronisé :
- Référence au thread qui contient actuellement le verrou.
- Référence à une file d’attente prête, qui contient les threads prêts à obtenir le verrou.
- Référence à une file d’attente, qui contient les threads qui attendent la notification d’une modification dans l’état de l’objet verrouillé.
Monitor verrouille des objets (c'est-à-dire des types référence), mais pas des types valeur. Il est possible de passer un type valeur à Enter et à Exit, mais il est converti (boxed) séparément pour chaque appel. Étant donné que chaque appel crée un objet distinct, Enter n'est jamais bloqué, et le code qu'il est censé protéger n'est pas correctement synchronisé. Comme l’objet passé à Exit est en plus différent de l’objet passé à Enter, Monitor lève l’exception SynchronizationLockException avec le message suivant : « La méthode de synchronisation de l’objet a été appelée à partir d’un bloc de code non synchronisé ».
L'exemple de code suivant illustre ce problème. Il lance dix tâches, chacune d’elles restant en veille pendant 250 millisecondes seulement. Ensuite, chaque tâche met à jour une variable de compteur, nTasks
, qui sert à compter le nombre de tâches ayant été lancées et exécutées. nTasks
est une variable globale qui peut être modifiée par plusieurs tâches simultanément. Pour empêcher cela, un gestionnaire (monitor) est utilisé. Toutefois, chaque tâche lève une exception SynchronizationLockException, comme le montre le résultat de l'exemple.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example1
{
public static void Main()
{
int nTasks = 0;
List<Task> tasks = new List<Task>();
try
{
for (int ctr = 0; ctr < 10; ctr++)
tasks.Add(Task.Run(() =>
{ // Instead of doing some work, just sleep.
Thread.Sleep(250);
// Increment the number of tasks.
Monitor.Enter(nTasks);
try
{
nTasks += 1;
}
finally
{
Monitor.Exit(nTasks);
}
}));
Task.WaitAll(tasks.ToArray());
Console.WriteLine("{0} tasks started and executed.", nTasks);
}
catch (AggregateException e)
{
String msg = String.Empty;
foreach (var ie in e.InnerExceptions)
{
Console.WriteLine("{0}", ie.GetType().Name);
if (!msg.Contains(ie.Message))
msg += ie.Message + Environment.NewLine;
}
Console.WriteLine("\nException Message(s):");
Console.WriteLine(msg);
}
}
}
// The example displays the following output:
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
//
// Exception Message(s):
// Object synchronization method was called from an unsynchronized block of code.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example3
Public Sub Main()
Dim nTasks As Integer = 0
Dim tasks As New List(Of Task)()
Try
For ctr As Integer = 0 To 9
tasks.Add(Task.Run(Sub()
' Instead of doing some work, just sleep.
Thread.Sleep(250)
' Increment the number of tasks.
Monitor.Enter(nTasks)
Try
nTasks += 1
Finally
Monitor.Exit(nTasks)
End Try
End Sub))
Next
Task.WaitAll(tasks.ToArray())
Console.WriteLine("{0} tasks started and executed.", nTasks)
Catch e As AggregateException
Dim msg As String = String.Empty
For Each ie In e.InnerExceptions
Console.WriteLine("{0}", ie.GetType().Name)
If Not msg.Contains(ie.Message) Then
msg += ie.Message + Environment.NewLine
End If
Next
Console.WriteLine(vbCrLf + "Exception Message(s):")
Console.WriteLine(msg)
End Try
End Sub
End Module
' The example displays the following output:
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
'
' Exception Message(s):
' Object synchronization method was called from an unsynchronized block of code.
Chaque tâche lève une exception SynchronizationLockException, car la variable nTasks
est convertie (boxed) avant l'appel à la méthode Monitor.Enter dans chaque tâche. En d'autres termes, chaque appel de méthode est passé à une variable distincte, qui est indépendante des autres variables. nTasks
est de nouveau convertie (boxed) dans l'appel à la méthode Monitor.Exit. Cette opération crée encore dix variables boxed qui sont indépendantes les unes des autres, nTasks
, et les dix variables boxed dans l'appel à la méthode Monitor.Enter. L'exception est levée, car le code tente de libérer un verrou sur une nouvelle variable qui n'était pas précédemment verrouillée.
Vous pouvez convertir (box) une variable de type valeur avant d'appeler Enter et Exit, comme dans l'exemple suivant, et passer le même objet boxed aux deux méthodes, mais cette opération n'offre aucun avantage. En effet, les modifications apportées à la variable non convertie (unboxed) ne sont pas répercutées dans la copie convertie (boxed), et il n'est pas possible de modifier la valeur de cette copie.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
int nTasks = 0;
object o = nTasks;
List<Task> tasks = new List<Task>();
try {
for (int ctr = 0; ctr < 10; ctr++)
tasks.Add(Task.Run( () => { // Instead of doing some work, just sleep.
Thread.Sleep(250);
// Increment the number of tasks.
Monitor.Enter(o);
try {
nTasks++;
}
finally {
Monitor.Exit(o);
}
} ));
Task.WaitAll(tasks.ToArray());
Console.WriteLine("{0} tasks started and executed.", nTasks);
}
catch (AggregateException e) {
String msg = String.Empty;
foreach (var ie in e.InnerExceptions) {
Console.WriteLine("{0}", ie.GetType().Name);
if (! msg.Contains(ie.Message))
msg += ie.Message + Environment.NewLine;
}
Console.WriteLine("\nException Message(s):");
Console.WriteLine(msg);
}
}
}
// The example displays the following output:
// 10 tasks started and executed.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example2
Public Sub Main()
Dim nTasks As Integer = 0
Dim o As Object = nTasks
Dim tasks As New List(Of Task)()
Try
For ctr As Integer = 0 To 9
tasks.Add(Task.Run(Sub()
' Instead of doing some work, just sleep.
Thread.Sleep(250)
' Increment the number of tasks.
Monitor.Enter(o)
Try
nTasks += 1
Finally
Monitor.Exit(o)
End Try
End Sub))
Next
Task.WaitAll(tasks.ToArray())
Console.WriteLine("{0} tasks started and executed.", nTasks)
Catch e As AggregateException
Dim msg As String = String.Empty
For Each ie In e.InnerExceptions
Console.WriteLine("{0}", ie.GetType().Name)
If Not msg.Contains(ie.Message) Then
msg += ie.Message + Environment.NewLine
End If
Next
Console.WriteLine(vbCrLf + "Exception Message(s):")
Console.WriteLine(msg)
End Try
End Sub
End Module
' The example displays the following output:
' 10 tasks started and executed.
Lorsque vous sélectionnez un objet sur lequel synchroniser, vous devez verrouiller uniquement sur des objets privés ou internes. Le verrouillage sur des objets externes peut entraîner des blocages, car le code non lié peut choisir les mêmes objets à verrouiller à des fins différentes.
Notez que vous pouvez synchroniser sur un objet dans plusieurs domaines d’application si l’objet utilisé pour le verrou dérive de MarshalByRefObject.
La section critique
Utilisez les méthodes et Exit les Enter méthodes pour marquer le début et la fin d’une section critique.
Remarque
La fonctionnalité fournie par les méthodes et Exit les Enter méthodes est identique à celle fournie par l’instruction lock en C# et l’instruction SyncLock en Visual Basic, sauf que le langage construit encapsuler la Monitor.Enter(Object, Boolean) surcharge de méthode et la Monitor.Exit méthode dans un try
...finally
bloquer pour vous assurer que le moniteur est libéré.
Si la section critique est un ensemble d’instructions contiguës, le verrou acquis par la Enter méthode garantit que seul un thread unique peut exécuter le code entouré avec l’objet verrouillé. Dans ce cas, nous vous recommandons de placer ce code dans un try
bloc et de placer l’appel à la Exit méthode dans un finally
bloc. Cela garantit la libération du verrou même si une exception se produit. Le fragment de code suivant illustre ce modèle.
// Define the lock object.
var obj = new Object();
// Define the critical section.
Monitor.Enter(obj);
try
{
// Code to execute one thread at a time.
}
// catch blocks go here.
finally
{
Monitor.Exit(obj);
}
' Define the lock object.
Dim obj As New Object()
' Define the critical section.
Monitor.Enter(obj)
Try
' Code to execute one thread at a time.
' catch blocks go here.
Finally
Monitor.Exit(obj)
End Try
Cette fonctionnalité est généralement utilisée pour synchroniser l’accès à une méthode statique ou d’instance d’une classe.
Si une section critique s’étend sur une méthode entière, l’installation de verrouillage peut être obtenue en plaçant la System.Runtime.CompilerServices.MethodImplAttribute valeur sur la méthode et en spécifiant la Synchronized valeur dans le constructeur de System.Runtime.CompilerServices.MethodImplAttribute. Lorsque vous utilisez cet attribut, les appels de méthode et Exit les Enter appels de méthode ne sont pas nécessaires. Le fragment de code suivant illustre ce modèle :
[MethodImplAttribute(MethodImplOptions.Synchronized)]
void MethodToLock()
{
// Method implementation.
}
<MethodImplAttribute(MethodImplOptions.Synchronized)>
Sub MethodToLock()
' Method implementation.
End Sub
Notez que l’attribut entraîne la conservation du verrou par le thread actuel jusqu’à ce que la méthode retourne ; si le verrou peut être libéré plus tôt, utilisez la Monitor classe, l’instruction de verrou C# ou l’instruction Visual Basic SyncLock à l’intérieur de la méthode au lieu de l’attribut.
Bien qu’il soit possible pour les Enter instructions Exit qui verrouillent et libèrent un objet donné pour traverser les limites des membres ou des classes ou les deux, cette pratique n’est pas recommandée.
Pulse, PulseAll et Wait
Une fois qu’un thread possède le verrou et a entré la section critique que le verrou protège, il peut appeler les méthodes et Monitor.PulseMonitor.PulseAll les Monitor.Waitméthodes.
Lorsque le thread qui contient les appels Waitde verrou, le verrou est libéré et le thread est ajouté à la file d’attente de l’objet synchronisé. Le premier thread de la file d’attente prête, le cas échéant, acquiert le verrou et entre dans la section critique. Le thread appelé Wait est déplacé de la file d’attente vers la file d’attente prête lorsque la Monitor.Pulse ou la Monitor.PulseAll méthode est appelée par le thread qui contient le verrou (à déplacer, le thread doit être à la tête de la file d’attente). La Wait méthode retourne lorsque le thread appelant réapparaît le verrou.
Lorsque le thread qui contient les appels Pulsede verrou, le thread à la tête de la file d’attente est déplacé vers la file d’attente prête. L’appel à la PulseAll méthode déplace tous les threads de la file d’attente vers la file d’attente prête.
Moniteurs et les handles d’attente
Il est important de noter la distinction entre l’utilisation de la Monitor classe et WaitHandle des objets.
- La Monitor classe est purement managée, entièrement portable et peut être plus efficace en termes de configuration requise pour les ressources du système d’exploitation.
- Les objets WaitHandle représentent des objets d'attente de système d'exploitation et sont utiles pour la synchronisation entre le code managé et le code non managé. Ils exposent certaines fonctionnalités avancées de système d'exploitation, comme la possibilité d'attendre plusieurs objets à la fois.
Exemples
L’exemple suivant utilise la classe pour synchroniser l’accès Monitor à une seule instance d’un générateur de nombres aléatoires représenté par la Random classe. L’exemple crée dix tâches, chacune s’exécutant de manière asynchrone sur un thread de pool de threads. Chaque tâche génère 10 000 nombres aléatoires, calcule leur moyenne et met à jour deux variables de niveau procédure qui conservent un total en cours d’exécution du nombre de nombres aléatoires générés et leur somme. Une fois toutes les tâches exécutées, ces deux valeurs sont ensuite utilisées pour calculer la moyenne globale.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example2
{
public static void Main()
{
List<Task> tasks = new List<Task>();
Random rnd = new Random();
long total = 0;
int n = 0;
for (int taskCtr = 0; taskCtr < 10; taskCtr++)
tasks.Add(Task.Run(() =>
{
int[] values = new int[10000];
int taskTotal = 0;
int taskN = 0;
int ctr = 0;
Monitor.Enter(rnd);
// Generate 10,000 random integers
for (ctr = 0; ctr < 10000; ctr++)
values[ctr] = rnd.Next(0, 1001);
Monitor.Exit(rnd);
taskN = ctr;
foreach (var value in values)
taskTotal += value;
Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
Task.CurrentId, (taskTotal * 1.0) / taskN,
taskN);
Interlocked.Add(ref n, taskN);
Interlocked.Add(ref total, taskTotal);
}));
try
{
Task.WaitAll(tasks.ToArray());
Console.WriteLine("\nMean for all tasks: {0:N2} (N={1:N0})",
(total * 1.0) / n, n);
}
catch (AggregateException e)
{
foreach (var ie in e.InnerExceptions)
Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message);
}
}
}
// The example displays output like the following:
// Mean for task 1: 499.04 (N=10,000)
// Mean for task 2: 500.42 (N=10,000)
// Mean for task 3: 499.65 (N=10,000)
// Mean for task 8: 502.59 (N=10,000)
// Mean for task 5: 502.75 (N=10,000)
// Mean for task 4: 494.88 (N=10,000)
// Mean for task 7: 499.22 (N=10,000)
// Mean for task 10: 496.45 (N=10,000)
// Mean for task 6: 499.75 (N=10,000)
// Mean for task 9: 502.79 (N=10,000)
//
// Mean for all tasks: 499.75 (N=100,000)
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example4
Public Sub Main()
Dim tasks As New List(Of Task)()
Dim rnd As New Random()
Dim total As Long = 0
Dim n As Integer = 0
For taskCtr As Integer = 0 To 9
tasks.Add(Task.Run(Sub()
Dim values(9999) As Integer
Dim taskTotal As Integer = 0
Dim taskN As Integer = 0
Dim ctr As Integer = 0
Monitor.Enter(rnd)
' Generate 10,000 random integers.
For ctr = 0 To 9999
values(ctr) = rnd.Next(0, 1001)
Next
Monitor.Exit(rnd)
taskN = ctr
For Each value In values
taskTotal += value
Next
Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
Task.CurrentId, taskTotal / taskN,
taskN)
Interlocked.Add(n, taskN)
Interlocked.Add(total, taskTotal)
End Sub))
Next
Try
Task.WaitAll(tasks.ToArray())
Console.WriteLine()
Console.WriteLine("Mean for all tasks: {0:N2} (N={1:N0})",
(total * 1.0) / n, n)
Catch e As AggregateException
For Each ie In e.InnerExceptions
Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message)
Next
End Try
End Sub
End Module
' The example displays output like the following:
' Mean for task 1: 499.04 (N=10,000)
' Mean for task 2: 500.42 (N=10,000)
' Mean for task 3: 499.65 (N=10,000)
' Mean for task 8: 502.59 (N=10,000)
' Mean for task 5: 502.75 (N=10,000)
' Mean for task 4: 494.88 (N=10,000)
' Mean for task 7: 499.22 (N=10,000)
' Mean for task 10: 496.45 (N=10,000)
' Mean for task 6: 499.75 (N=10,000)
' Mean for task 9: 502.79 (N=10,000)
'
' Mean for all tasks: 499.75 (N=100,000)
Étant donné qu’elles sont accessibles à partir de n’importe quelle tâche s’exécutant sur un thread de pool de threads, l’accès aux variables total
et n
doit également être synchronisé. La Interlocked.Add méthode est utilisée à cet effet.
L’exemple suivant illustre l’utilisation combinée de la Monitor classe (implémentée avec la lock
construction de langage), SyncLock
de la Interlocked classe et de la AutoResetEvent classe. Il définit deux classes internal
(en C#) ou Friend
(en Visual Basic), SyncResource
et UnSyncResource
, qui fournissent respectivement un accès synchronisé et non synchronisé à une ressource. Pour garantir que l’exemple illustre la différence entre l’accès synchronisé et non synchronisé (ce qui pourrait être le cas si chaque appel de méthode se termine rapidement), la méthode inclut un délai aléatoire : pour les threads dont la propriété Thread.ManagedThreadId est paire, la méthode appelle Thread.Sleep pour introduire un délai de 2000 millisecondes. La classe SyncResource
n’étant pas publique, aucune partie du code client n’acquiert un verrou sur la ressource synchronisée. C’est la classe interne proprement dite qui acquiert le verrou. Cela empêche que du code malveillant acquière un verrou sur un objet public.
using System;
using System.Threading;
internal class SyncResource
{
// Use a monitor to enforce synchronization.
public void Access()
{
lock(this) {
Console.WriteLine("Starting synchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId);
if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
Thread.Sleep(2000);
Thread.Sleep(200);
Console.WriteLine("Stopping synchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId);
}
}
}
internal class UnSyncResource
{
// Do not enforce synchronization.
public void Access()
{
Console.WriteLine("Starting unsynchronized resource access on Thread #{0}",
Thread.CurrentThread.ManagedThreadId);
if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
Thread.Sleep(2000);
Thread.Sleep(200);
Console.WriteLine("Stopping unsynchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId);
}
}
public class App
{
private static int numOps;
private static AutoResetEvent opsAreDone = new AutoResetEvent(false);
private static SyncResource SyncRes = new SyncResource();
private static UnSyncResource UnSyncRes = new UnSyncResource();
public static void Main()
{
// Set the number of synchronized calls.
numOps = 5;
for (int ctr = 0; ctr <= 4; ctr++)
ThreadPool.QueueUserWorkItem(new WaitCallback(SyncUpdateResource));
// Wait until this WaitHandle is signaled.
opsAreDone.WaitOne();
Console.WriteLine("\t\nAll synchronized operations have completed.\n");
// Reset the count for unsynchronized calls.
numOps = 5;
for (int ctr = 0; ctr <= 4; ctr++)
ThreadPool.QueueUserWorkItem(new WaitCallback(UnSyncUpdateResource));
// Wait until this WaitHandle is signaled.
opsAreDone.WaitOne();
Console.WriteLine("\t\nAll unsynchronized thread operations have completed.\n");
}
static void SyncUpdateResource(Object state)
{
// Call the internal synchronized method.
SyncRes.Access();
// Ensure that only one thread can decrement the counter at a time.
if (Interlocked.Decrement(ref numOps) == 0)
// Announce to Main that in fact all thread calls are done.
opsAreDone.Set();
}
static void UnSyncUpdateResource(Object state)
{
// Call the unsynchronized method.
UnSyncRes.Access();
// Ensure that only one thread can decrement the counter at a time.
if (Interlocked.Decrement(ref numOps) == 0)
// Announce to Main that in fact all thread calls are done.
opsAreDone.Set();
}
}
// The example displays output like the following:
// Starting synchronized resource access on thread #6
// Stopping synchronized resource access on thread #6
// Starting synchronized resource access on thread #7
// Stopping synchronized resource access on thread #7
// Starting synchronized resource access on thread #3
// Stopping synchronized resource access on thread #3
// Starting synchronized resource access on thread #4
// Stopping synchronized resource access on thread #4
// Starting synchronized resource access on thread #5
// Stopping synchronized resource access on thread #5
//
// All synchronized operations have completed.
//
// Starting unsynchronized resource access on Thread #7
// Starting unsynchronized resource access on Thread #9
// Starting unsynchronized resource access on Thread #10
// Starting unsynchronized resource access on Thread #6
// Starting unsynchronized resource access on Thread #3
// Stopping unsynchronized resource access on thread #7
// Stopping unsynchronized resource access on thread #9
// Stopping unsynchronized resource access on thread #3
// Stopping unsynchronized resource access on thread #10
// Stopping unsynchronized resource access on thread #6
//
// All unsynchronized thread operations have completed.
Imports System.Threading
Friend Class SyncResource
' Use a monitor to enforce synchronization.
Public Sub Access()
SyncLock Me
Console.WriteLine("Starting synchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId)
If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
Thread.Sleep(2000)
End If
Thread.Sleep(200)
Console.WriteLine("Stopping synchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId)
End SyncLock
End Sub
End Class
Friend Class UnSyncResource
' Do not enforce synchronization.
Public Sub Access()
Console.WriteLine("Starting unsynchronized resource access on Thread #{0}",
Thread.CurrentThread.ManagedThreadId)
If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
Thread.Sleep(2000)
End If
Thread.Sleep(200)
Console.WriteLine("Stopping unsynchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId)
End Sub
End Class
Public Module App
Private numOps As Integer
Private opsAreDone As New AutoResetEvent(False)
Private SyncRes As New SyncResource()
Private UnSyncRes As New UnSyncResource()
Public Sub Main()
' Set the number of synchronized calls.
numOps = 5
For ctr As Integer = 0 To 4
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf SyncUpdateResource))
Next
' Wait until this WaitHandle is signaled.
opsAreDone.WaitOne()
Console.WriteLine(vbTab + Environment.NewLine + "All synchronized operations have completed.")
Console.WriteLine()
numOps = 5
' Reset the count for unsynchronized calls.
For ctr As Integer = 0 To 4
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf UnSyncUpdateResource))
Next
' Wait until this WaitHandle is signaled.
opsAreDone.WaitOne()
Console.WriteLine(vbTab + Environment.NewLine + "All unsynchronized thread operations have completed.")
End Sub
Sub SyncUpdateResource()
' Call the internal synchronized method.
SyncRes.Access()
' Ensure that only one thread can decrement the counter at a time.
If Interlocked.Decrement(numOps) = 0 Then
' Announce to Main that in fact all thread calls are done.
opsAreDone.Set()
End If
End Sub
Sub UnSyncUpdateResource()
' Call the unsynchronized method.
UnSyncRes.Access()
' Ensure that only one thread can decrement the counter at a time.
If Interlocked.Decrement(numOps) = 0 Then
' Announce to Main that in fact all thread calls are done.
opsAreDone.Set()
End If
End Sub
End Module
' The example displays output like the following:
' Starting synchronized resource access on thread #6
' Stopping synchronized resource access on thread #6
' Starting synchronized resource access on thread #7
' Stopping synchronized resource access on thread #7
' Starting synchronized resource access on thread #3
' Stopping synchronized resource access on thread #3
' Starting synchronized resource access on thread #4
' Stopping synchronized resource access on thread #4
' Starting synchronized resource access on thread #5
' Stopping synchronized resource access on thread #5
'
' All synchronized operations have completed.
'
' Starting unsynchronized resource access on Thread #7
' Starting unsynchronized resource access on Thread #9
' Starting unsynchronized resource access on Thread #10
' Starting unsynchronized resource access on Thread #6
' Starting unsynchronized resource access on Thread #3
' Stopping unsynchronized resource access on thread #7
' Stopping unsynchronized resource access on thread #9
' Stopping unsynchronized resource access on thread #3
' Stopping unsynchronized resource access on thread #10
' Stopping unsynchronized resource access on thread #6
'
' All unsynchronized thread operations have completed.
L’exemple définit une variable, numOps
, qui définit le nombre de threads qui tenteront d’accéder à la ressource. Le thread d’application appelle la méthode ThreadPool.QueueUserWorkItem(WaitCallback) à cinq reprises pour l’accès synchronisé et non synchronisé. La méthode ThreadPool.QueueUserWorkItem(WaitCallback) possède un seul paramètre, un délégué qui n’accepte aucun paramètre et ne retourne aucune valeur. Pour l’accès synchronisé, elle appelle la méthode SyncUpdateResource
. Pour l’accès non synchronisé, elle appelle la méthode UnSyncUpdateResource
. Après chaque ensemble d’appels de méthode, le thread d’application appelle la méthode AutoResetEvent.WaitOne afin qu’elle bloque jusqu’à ce que l’instance AutoResetEvent soit signalée.
Chaque appel à la méthode SyncUpdateResource
appelle la méthode SyncResource.Access
interne, puis appelle la méthode Interlocked.Decrement pour décrémenter le compteur numOps
. La Interlocked.Decrement méthode est utilisée pour décrémenter le compteur, car sinon vous ne pouvez pas être certain qu’un deuxième thread accède à la valeur avant que la valeur décrémentée d’un premier thread ait été stockée dans la variable. Lorsque le dernier thread de travail synchronisé décrémente le compteur à zéro, indiquant que tous les threads synchronisés ont terminé d’accéder à la ressource, la SyncUpdateResource
méthode appelle la EventWaitHandle.Set méthode, ce qui signale au thread principal de poursuivre l’exécution.
Chaque appel à la méthode UnSyncUpdateResource
appelle la méthode UnSyncResource.Access
interne, puis appelle la méthode Interlocked.Decrement pour décrémenter le compteur numOps
. Une fois de plus, la Interlocked.Decrement méthode est utilisée pour décrémenter le compteur pour s’assurer qu’un deuxième thread n’accède pas à la valeur avant que la valeur décrémentée d’un premier thread ait été affectée à la variable. Lorsque le dernier thread de travail non synchronisé décrémente le compteur à zéro, indiquant qu’aucun thread non synchronisé n’a besoin d’accéder à la ressource, la UnSyncUpdateResource
méthode appelle la EventWaitHandle.Set méthode, ce qui signale au thread principal de continuer l’exécution.
Comme le montre la sortie de l’exemple, l’accès synchronisé garantit que le thread appelant quitte la ressource protégée avant qu’un autre thread puisse y accéder ; chaque thread attend son prédécesseur. En revanche, sans verrou la méthode UnSyncResource.Access
est appelée dans l’ordre dans lequel les threads l’atteignent.