Monitor Classe

Définition

Fournit un mécanisme qui synchronise l'accès aux objets.

public ref class Monitor abstract sealed
public ref class Monitor sealed
public static class Monitor
public sealed class Monitor
[System.Runtime.InteropServices.ComVisible(true)]
public static class Monitor
type Monitor = class
[<System.Runtime.InteropServices.ComVisible(true)>]
type Monitor = class
Public Class Monitor
Public NotInheritable Class Monitor
Héritage
Monitor
Attributs

Exemples

L’exemple suivant utilise la Monitor classe pour synchroniser l’accès à une instance unique 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 façon asynchrone sur un thread de pool de threads. Chaque tâche génère des nombres aléatoires de 10 000, calcule leur moyenne et met à jour deux variables de niveau procédure qui maintiennent un total cumulé du nombre de nombres aléatoires générés et leur somme. Une fois que toutes les tâches ont été 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 Example
{
   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 Example
   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’ils sont accessibles à partir de n’importe quelle tâche exécutée 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 lock la SyncLock construction de langage ou), 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’il se bloque jusqu’à ce que l' AutoResetEvent instance 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 dans le cas contraire, vous ne pouvez pas être certain qu’un deuxième thread accédera à la valeur avant que la valeur décrémentée d’un premier thread soit stockée dans la variable. Lorsque le dernier thread de travail synchronisé décrémente le compteur à zéro, ce qui indique que tous les threads synchronisés ont terminé d’accéder à la ressource, la SyncUpdateResource méthode appelle la EventWaitHandle.Set méthode, qui indique au thread principal de continuer 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 encore, la Interlocked.Decrement méthode est utilisée pour décrémenter le compteur afin de garantir qu’un deuxième thread n’accède pas à la valeur avant qu’une valeur décrémentée du premier thread n’ait été assignée à la variable. Lorsque le dernier thread de travail non synchronisé décrémente le compteur à zéro, ce qui indique qu’aucun thread plus non synchronisé n’a besoin d’accéder à la ressource, la UnSyncUpdateResource méthode appelle la EventWaitHandle.Set méthode, qui signale au thread principal qu’il doit poursuivre 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.

Remarques

La Monitor classe vous permet de synchroniser l’accès à une région de code en adoptant et en libérant un verrou sur un objet particulier en appelant les Monitor.Enter Monitor.TryEnter méthodes, et Monitor.Exit . Les verrous d’objets permettent de restreindre l’accès à un bloc de code, communément appelé section critique. Tandis 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 en cours d’exécution par le propriétaire du verrou, à moins que l’autre thread exécute le code à l’aide d’un objet verrouillé différent.

Contenu de cet article :

Classe Monitor : vue d’ensemble
Objet Lock
Section critique
Pulse, PulseAll et Wait
Moniteurs et les handles d’attente

Classe Monitor : vue d’ensemble

Monitor présente les caractéristiques suivantes :

  • Il est associé à un objet à la demande.

  • Il est indépendant, ce qui signifie qu’il peut être appelé directement à partir de n’importe quel contexte.

  • Une instance de la Monitor classe ne peut pas être créée ; les méthodes de la Monitor classe sont toutes statiques. Chaque méthode reçoit l’objet synchronisé qui contrôle l’accès à la section critique.

Notes

Utilisez la Monitor classe pour verrouiller des objets autres que des chaînes (autrement dit, des types référence autres que String ), et non des types valeur. Pour plus d’informations, consultez les surcharges de la Enter méthode et la section objet Lock plus loin dans cet article.

Le tableau suivant décrit les actions qui peuvent être effectuées par les 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 à moins d’exécuter les instructions de la section critique à l’aide d’un objet verrouillé différent.
Wait Libère le verrou sur un objet afin d’autoriser d’autres threads à verrouiller et à accéder à l’objet. Le thread appelant attend qu’un autre thread accède à l’objet. Les signaux d’impulsions sont utilisés pour notifier 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 opérationnelle 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é.

à partir du .NET Framework 4, il existe deux ensembles de surcharges pour les Enter TryEnter méthodes et. un ensemble de surcharges a un ref paramètre (en C#) ou ByRef (dans Visual Basic) Boolean qui est défini de manière atomique 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 que le verrou protège ne sont pas dans un état cohérent.

Objet Lock

la classe Monitor se compose des static méthodes (en C#) ou Shared (dans Visual Basic) qui opèrent 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 détient actuellement le verrou.

  • Référence à une file d’attente opérationnelle, qui contient les threads prêts à obtenir le verrou.

  • Référence à une file d’attente en attente, qui contient les threads en attente de notification d’une modification de 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 Example
{
   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 Example
   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 Example
   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 les objets privés ou internes. Le verrouillage sur des objets externes peut entraîner des interblocages, car un 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 Enter Exit méthodes et pour marquer le début et la fin d’une section critique.

Notes

les fonctionnalités fournies par les Enter Exit méthodes et sont identiques à celles fournies par l’instruction lock en C# et l’instruction SyncLock dans Visual Basic, sauf que les constructions de langage encapsulent la surcharge de Monitor.Enter(Object, Boolean) méthode et la Monitor.Exit méthode dans un try ...finally bloquer pour vous assurer que le moniteur est relâché.

Si la section critique est un ensemble d’instructions contiguës, le verrou acquis par la Enter méthode garantit qu’un seul thread peut exécuter le code délimité 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 d’instance ou statique d’une classe.

Si une section critique couvre une méthode entière, la fonctionnalité de verrouillage peut être obtenue en plaçant le System.Runtime.CompilerServices.MethodImplAttribute 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 Enter appels de méthode et ne Exit 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 fait que le thread actuel conserve le verrou jusqu’à ce que la méthode soit retournée ; si le verrou peut être libéré plus tôt, utilisez la Monitor classe, l’instruction lock 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 Enter les Exit instructions et qui verrouillent et libèrent un objet donné pour franchir les limites d’un membre ou d’une classe, ou les deux, cette pratique n’est pas recommandée.

Pulse, PulseAll et Wait

Une fois qu’un thread possède le verrou et qu’il a entré la section critique protégée par le verrou, il peut appeler les Monitor.Wait Monitor.Pulse méthodes, et Monitor.PulseAll .

Lorsque le thread qui contient le verrou appelle Wait , le verrou est libéré et le thread est ajouté à la file d’attente en attente de l’objet synchronisé. Le premier thread de la file d’attente opérationnelle, le cas échéant, acquiert le verrou et entre dans la section critique. Le thread qui Wait a appelé est déplacé de la file d’attente en attente vers la file d’attente opérationnelle lorsque la Monitor.Pulse Monitor.PulseAll méthode ou est appelée par le thread qui détient le verrou (à déplacer, le thread doit se trouver au début de la file d’attente en attente). La Wait méthode retourne lorsque le thread appelant réacquière le verrou.

Lorsque le thread qui contient le verrou appelle Pulse , le thread situé à l’en-tête de la file d’attente en attente est déplacé vers la file d’attente opérationnelle. L’appel à la PulseAll méthode déplace tous les threads de la file d’attente en attente vers la file d’attente opérationnelle.

Moniteurs et les handles d’attente

Il est important de noter la différence entre l’utilisation de la Monitor classe et les WaitHandle objets.

  • La Monitor classe est purement managée, entièrement portable et peut être plus efficace en termes de besoins en 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.

Propriétés

LockContentionCount

Obtient le nombre de fois où il y a eu de la contention lors des tentatives de prendre le verrou du moniteur.

Méthodes

Enter(Object)

Acquiert un verrou exclusif sur l'objet spécifié.

Enter(Object, Boolean)

Acquiert un verrou exclusif sur l'objet spécifié et définit de manière atomique une valeur qui indique si le verrou a été pris.

Exit(Object)

Libère un verrou exclusif sur l'objet spécifié.

IsEntered(Object)

Détermine si le thread actuel détient le verrou sur l'objet spécifié.

Pulse(Object)

Avertit un thread situé dans la file d'attente en suspens d'un changement d'état de l'objet verrouillé.

PulseAll(Object)

Avertit tous les threads en attente d'un changement d'état de l'objet.

TryEnter(Object)

Essaie d'acquérir un verrou exclusif sur l'objet spécifié.

TryEnter(Object, Boolean)

Tente d'acquérir un verrou exclusif sur l'objet spécifié et définit de manière atomique une valeur qui indique si le verrou a été pris.

TryEnter(Object, Int32)

Tentatives d'acquisition d'un verrou exclusif sur l'objet spécifié au cours du nombre spécifié de millisecondes.

TryEnter(Object, Int32, Boolean)

Tente, pendant le nombre spécifié de millisecondes, d'acquérir un verrou exclusif sur l'objet spécifié et définit de manière atomique une valeur qui indique si le verrou a été pris.

TryEnter(Object, TimeSpan)

Tentatives d'acquisition d'un verrou exclusif sur l'objet spécifié au cours de la période spécifiée.

TryEnter(Object, TimeSpan, Boolean)

Tente, pendant le délai spécifié, d'acquérir un verrou exclusif sur l'objet spécifié et définit de manière atomique une valeur qui indique si le verrou a été pris.

Wait(Object)

Libère le verrou d’un objet et bloque le thread actuel jusqu’à ce qu’il acquière à nouveau le verrou.

Wait(Object, Int32)

Libère le verrou d’un objet et bloque le thread actuel jusqu’à ce qu’il acquière à nouveau le verrou. Si le délai d'attente spécifié est écoulé, le thread intègre la file d'attente opérationnelle.

Wait(Object, Int32, Boolean)

Libère le verrou d’un objet et bloque le thread actuel jusqu’à ce qu’il acquière à nouveau le verrou. Si le délai d'attente spécifié est écoulé, le thread intègre la file d'attente opérationnelle. Cette méthode spécifie également si le domaine de synchronisation associé au contexte (dans le cas d’un contexte synchronisé) est abandonné avant l’attente et acquis à nouveau par la suite.

Wait(Object, TimeSpan)

Libère le verrou d’un objet et bloque le thread actuel jusqu’à ce qu’il acquière à nouveau le verrou. Si le délai d'attente spécifié est écoulé, le thread intègre la file d'attente opérationnelle.

Wait(Object, TimeSpan, Boolean)

Libère le verrou d’un objet et bloque le thread actuel jusqu’à ce qu’il acquière à nouveau le verrou. Si le délai d'attente spécifié est écoulé, le thread intègre la file d'attente opérationnelle. Le domaine de synchronisation associé au contexte synchronisé peut être abandonné avant l’attente et acquis de nouveau par la suite.

S’applique à

Cohérence de thread

Ce type est thread-safe.

Voir aussi