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.
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 Monitor.Enter, Monitor.TryEnter et Monitor.Exit. 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.
Aperçu
Monitor présente les fonctionnalités 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 classe Monitor pour verrouiller des objets autres que des chaînes (c'est-à-dire, des types référence autres que String), et non des types valeur. Pour plus d’informations, consultez les surcharges de la méthode Enter et la section L’objet 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 | Descriptif |
---|---|
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 | Déverrouille un objet afin de 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 vérifier 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 Enter et TryEnter. Un ensemble de surcharges comporte un paramètre ref
(en C#) ou ByRef
(en Visual Basic) Boolean qui est atomiquement défini sur 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.
- Une 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 les objets (c’est-à-dire les types référence), et non les 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 ne bloque jamais et que le code qu’il protège n’est pas vraiment synchronisé. En outre, l’objet passé à est différent de l’objet passé à Exit, donc Enter lève Monitor une exception avec le message « Méthode de synchronisation d’objet a été appelée à SynchronizationLockException partir d’un bloc de code non synchronisé ».
L’exemple suivant illustre ce problème. Il lance dix tâches, chacune d’entre elles étant en veille pendant 250 millisecondes. Chaque tâche met ensuite à jour une variable de compteur, nTasks
qui est destinée à compter le nombre de tâches réellement lancées et exécutées. Étant nTasks
donné qu’il s’agit d’une variable globale qui peut être mise à jour simultanément par plusieurs tâches, un moniteur est utilisé pour le protéger contre la modification simultanée par plusieurs tâches. 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($"{nTasks} tasks started and executed.");
}
catch (AggregateException e)
{
String msg = String.Empty;
foreach (var ie in e.InnerExceptions)
{
Console.WriteLine($"{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 reçoit une variable distincte indépendante des autres.
nTasks
est de nouveau convertie (boxed) dans l'appel à la méthode Monitor.Exit. Une fois de plus, cela crée dix nouvelles variables boxed, qui sont indépendantes les unes des autres, nTasks
et les dix variables boxed créées dans l’appel à la méthode Monitor.Enter. L’exception est levée, alors, car notre code tente de libérer un verrou sur une variable nouvellement créée qui n’a pas été précédemment verrouillée.
Bien que vous puissiez boxer une variable de type valeur avant d’appeler Enter et Exit, comme illustré dans l’exemple suivant, et passer le même objet encapsulé aux deux méthodes, il n’y a aucun avantage à effectuer cette opération. Les modifications apportées à la variable nonboxée ne sont pas reflétées dans la copie boxée et il n’existe aucun moyen de modifier la valeur de la copie boxée.
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($"{nTasks} tasks started and executed.");
}
catch (AggregateException e) {
String msg = String.Empty;
foreach (var ie in e.InnerExceptions) {
Console.WriteLine($"{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.
Section critique
Utilisez les méthodes Enter et Exit pour marquer le début et la fin d'une section critique.
Remarque
La fonctionnalité fournie par les méthodes Enter et Exit est identique à celle fournie par l’instruction lock en C# et l’instruction SyncLock en Visual Basic, sauf que les constructions de langage encapsulent la surcharge de méthode Monitor.Enter(Object, Boolean) et la méthode Monitor.Exit dans un try
...
finally
bloquer afin de libérer le moniteur.
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 que le verrou est libéré 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, le mécanisme de verrouillage peut être réalisé en plaçant System.Runtime.CompilerServices.MethodImplAttribute sur la méthode et en spécifiant la valeur de Synchronized dans le constructeur de System.Runtime.CompilerServices.MethodImplAttribute. Lorsque vous utilisez cet attribut, les appels de méthode Enter et Exit 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 que les instructions Enter et Exit qui verrouillent et libèrent un objet donné traversent 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 est entré dans la section critique que le verrou protège, il peut appeler les méthodes Monitor.Wait, Monitor.Pulse et Monitor.PulseAll.
Lorsque le thread qui détient le verrou appelle Wait, 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 qui a appelé Wait est déplacé de la file d’attente vers la file d’attente prête lorsque la méthode Monitor.Pulse ou la méthode Monitor.PulseAll est appelée par le thread qui contient le verrou (à déplacer, le thread doit être en tête de la file d’attente). La méthode Wait retourne lorsque le verrou est récupéré par le thread appelant.
Lorsque le thread qui détient le verrou appelle Pulse, le thread en 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 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.
- WaitHandle les objets représentant des objets attendables du système d’exploitation, sont utiles pour la synchronisation entre le code managé et non managé, et exposent certaines fonctionnalités avancées du système d’exploitation, telles que la possibilité d’attendre plusieurs objets à la fois.
Exemples
L’exemple suivant utilise la classe Monitor pour synchroniser l’accès à une seule instance d’un générateur de nombres aléatoires représenté par la classe Random. L'exemple crée dix tâches, chacune exécutée de manière asynchrone sur un thread du 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: {(total * 1.0) / n:N2} (N={n:N0})");
}
catch (AggregateException e)
{
foreach (var ie in e.InnerExceptions)
Console.WriteLine($"{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 classe Monitor (implémentée avec la construction de langage lock
ou SyncLock
), de la classe Interlocked et de la classe AutoResetEvent. Il définit deux internal
classes Friend
(en C#) ou SyncResource
(en Visual Basic) et UnSyncResource
, qui fournissent respectivement un accès synchronisé et non synchronisé à une ressource. Pour vous assurer que l’exemple illustre la différence entre l’accès synchronisé et non synchronisé (ce qui peut ê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 2 000 millisecondes. Notez que, étant donné que la SyncResource
classe n’est pas publique, aucun du code client ne prend de verrou sur la ressource synchronisée ; la classe interne elle-même prend le verrou. Cela empêche le code malveillant de prendre 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 #{Thread.CurrentThread.ManagedThreadId}");
if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
Thread.Sleep(2000);
Thread.Sleep(200);
Console.WriteLine($"Stopping synchronized resource access on thread #{Thread.CurrentThread.ManagedThreadId}");
}
}
}
internal class UnSyncResource
{
// Do not enforce synchronization.
public void Access()
{
Console.WriteLine($"Starting unsynchronized resource access on Thread #{Thread.CurrentThread.ManagedThreadId}");
if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
Thread.Sleep(2000);
Thread.Sleep(200);
Console.WriteLine($"Stopping unsynchronized resource access on thread #{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) pour l’accès synchronisé et l’accès non synchronisé, cinq fois chacun. La ThreadPool.QueueUserWorkItem(WaitCallback) méthode a un paramètre unique, un délégué qui n’accepte aucun paramètre et ne retourne aucune valeur. Pour l’accès synchronisé, il appelle la SyncUpdateResource
méthode ; pour un accès non synchronisé, il appelle la UnSyncUpdateResource
méthode. 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 de la méthode SyncUpdateResource
appelle la méthode interne SyncResource.Access
, 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 de la méthode UnSyncUpdateResource
appelle la méthode interne UnSyncResource.Access
, 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 le verrou, la UnSyncResource.Access
méthode est appelée dans l’ordre dans lequel les threads l’atteignent.