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 classe pour synchroniser l’accès Monitor à 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 10 000 nombres aléatoires, calcule leur moyenne et met à jour deux variables au niveau de la 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 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 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 du 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 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 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 indique 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.

Remarques

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 le Monitor.Enter, Monitor.TryEnteret Monitor.Exit les méthodes. 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 en cours d’exécution par le propriétaire du verrou, sauf si l’autre thread exécute le code à l’aide d’un autre objet verrouillé.

Contenu de cet article :

Classe Monitor : vue d’ensemble
L’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 est passée à 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 méthode et de la section de l’objet Enter de verrouillage 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 afin d’autoriser d’autres threads à verrouiller et à 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 concernant les 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é.

À compter de .NET Framework 4, 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 true atomiquement 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.

L’objet lock

La classe Monitor se compose de static méthodes (en C#) ou Shared (en 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 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 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 sur des objets privés ou internes. Le verrouillage sur des objets externes peut entraîner des interblocages, 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.

Notes

Les fonctionnalités fournies par les méthodes et Exit les Enter méthodes sont identiques à celles fournies par l’instruction lock en C# et l’instruction SyncLock en Visual Basic, sauf que le langage construit encapsule la Monitor.Enter(Object, Boolean) surcharge de méthode et la Monitor.Exit méthode dans un try...finally 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 qu’un seul thread 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 de Enter 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 le blocage du 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 qu’il est entré dans la section critique que le verrou protège, il peut appeler les méthodes , Monitor.Pulseet Monitor.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 en attente vers la file d’attente prête lorsque la ou la Monitor.Pulse 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éacquire 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 des 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