Monitor Clase

Definición

Proporciona un mecanismo que sincroniza el acceso a los objetos.

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
Herencia
Monitor
Atributos

Ejemplos

En el ejemplo siguiente se usa la clase para sincronizar el Monitor acceso a una sola instancia de un generador de números aleatorios representado por la Random clase . En el ejemplo se crean diez tareas, cada una de las cuales se ejecuta de forma asincrónica en un subproceso del grupo de subprocesos. Cada tarea genera 10 000 números aleatorios, calcula su promedio y actualiza dos variables de nivel de procedimiento que mantienen un total en ejecución del número de números aleatorios generados y su suma. Una vez ejecutadas todas las tareas, estos dos valores se usan para calcular la media general.

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)

Dado que se puede tener acceso a ellas desde cualquier tarea que se ejecute en un subproceso del grupo de subprocesos, el acceso a las variables total y n también debe sincronizarse. El Interlocked.Add método se usa para este propósito.

En el ejemplo siguiente se muestra el uso combinado de la Monitor clase (implementada con la lock construcción de lenguaje o SyncLock ), la Interlocked clase y la AutoResetEvent clase . Define dos clases de tipo internal (en C#) o Friend (en Visual Basic), SyncResource y UnSyncResource, que proporcionan respectivamente acceso sincronizado y sin sincronizar a un recurso. Para garantizar que el ejemplo ilustra la diferencia entre el acceso sincronizado y sin sincronizar (podría darse el caso si cada llamada al método se realiza rápidamente), el método incluye un retraso aleatorio: para subprocesos cuya propiedad Thread.ManagedThreadId es par, el método llama a Thread.Sleep para insertar un retraso de 2.000 milisegundos. Tenga en cuenta que, dado que la clase SyncResource no es pública, ninguno de los códigos de cliente tiene un bloqueo en el recurso sincronizado; esto es, la propia clase interna se hace cargo del bloqueo. Esto evita que cualquier código malintencionado se haga cargo de un bloqueo en un objeto público.

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.

En el ejemplo se define la variable numOps, que define el número de subprocesos que intentarán tener acceso al recurso. El subproceso de la aplicación llama al método ThreadPool.QueueUserWorkItem(WaitCallback) para obtener acceso sincronizado y sin sincronizar; esto se realizará hasta un máximo de cinco veces con cada opción. El método ThreadPool.QueueUserWorkItem(WaitCallback) tiene un único parámetro; un delegado que no acepta parámetros y que no devuelve valor alguno. Para obtener acceso sincronizado, este elemento invoca al método SyncUpdateResource; en cambio, para obtener acceso no sincronizado, invoca al método UnSyncUpdateResource. Después de cada conjunto de llamadas de método, el subproceso de aplicación llama al método AutoResetEvent.WaitOne para que se bloquee hasta que se señale la AutoResetEvent instancia.

Cada llamada al métodoSyncUpdateResource, llama al método interno SyncResource.Access y, a continuación, llama al método Interlocked.Decrement para reducir el contador numOps. El Interlocked.Decrement método Se usa para disminuir el contador, ya que de lo contrario no puede estar seguro de que un segundo subproceso tendrá acceso al valor antes de que se haya almacenado el valor reducido de un primer subproceso en la variable . Cuando el último subproceso de trabajo sincronizado disminuye el contador en cero, lo que indica que todos los subprocesos sincronizados han completado el acceso al recurso, el SyncUpdateResource método llama al EventWaitHandle.Set método , que indica que el subproceso principal continúe la ejecución.

Cada llamada al métodoUnSyncUpdateResource, llama al método interno UnSyncResource.Access y, a continuación, llama al método Interlocked.Decrement para reducir el contador numOps. Una vez más, el Interlocked.Decrement método Se usa para disminuir el contador para asegurarse de que un segundo subproceso no tiene acceso al valor antes de que se haya asignado el valor reducido de un primer subproceso a la variable. Cuando el último subproceso de trabajo sin sincronizar reduce el contador a cero, lo que indica que no es necesario tener acceso al recurso más subprocesos sin sincronizar, el UnSyncUpdateResource método llama al EventWaitHandle.Set método , que indica que el subproceso principal continúe la ejecución.

Como se muestra en el resultado del ejemplo, el acceso sincronizado garantiza que el subproceso de llamada saldrá del recurso protegido antes de que otro subproceso pueda tener acceso a él; así pues, cada subproceso esperará a su predecesor. Por otro lado, sin este bloqueo, se llamará al método UnSyncResource.Access en el orden en el que los subprocesos accedan a él.

Comentarios

La Monitor clase permite sincronizar el acceso a una región de código tomando y liberando un bloqueo en un objeto determinado llamando a los Monitor.Entermétodos , Monitor.TryEntery Monitor.Exit . Los bloqueos de objeto proporcionan la capacidad de restringir el acceso a un bloque de código, normalmente denominado sección crítica. Aunque un subproceso posee el bloqueo de un objeto, ningún otro subproceso puede adquirir ese bloqueo. También puede usar la Monitor clase para asegurarse de que ningún otro subproceso pueda acceder a una sección del código de aplicación que ejecuta el propietario del bloqueo, a menos que el otro subproceso ejecute el código mediante un objeto bloqueado diferente.

En este artículo:

La clase Monitor: Información general
El objeto lock
Sección crítica
Pulse, PulseAll y Wait
Monitores y identificadores de espera

La clase Monitor: Información general

Monitor tiene las siguientes características:

  • Está asociado a un objeto a petición.

  • No está enlazado, lo que significa que se puede llamar directamente desde cualquier contexto.

  • No se puede crear una instancia de la Monitor clase; todos los métodos de la Monitor clase son estáticos. Cada método se pasa el objeto sincronizado que controla el acceso a la sección crítica.

Nota

Use la Monitor clase para bloquear objetos distintos de cadenas (es decir, tipos de referencia distintos Stringde ), no tipos de valor. Para obtener más información, consulte la sección Sobrecargas del Enter método y El objeto lock más adelante en este artículo.

En la tabla siguiente se describen las acciones que pueden realizar los subprocesos que acceden a objetos sincronizados:

Acción Descripción
Enter, TryEnter Adquiere un bloqueo para un objeto . Esta acción también marca el principio de una sección crítica. Ningún otro subproceso puede escribir la sección crítica a menos que ejecute las instrucciones de la sección crítica mediante un objeto bloqueado diferente.
Wait Libera el bloqueo en un objeto para permitir que otros subprocesos bloqueen y accedan al objeto. El subproceso que llama espera mientras otro subproceso tiene acceso al objeto . Las señales de pulso se usan para notificar a los subprocesos en espera los cambios en el estado de un objeto.
Pulse (señal), PulseAll Envía una señal a uno o varios subprocesos en espera. La señal notifica a un subproceso en espera que el estado del objeto bloqueado ha cambiado y el propietario del bloqueo está listo para liberar el bloqueo. El subproceso en espera se coloca en la cola lista del objeto para que finalmente reciba el bloqueo del objeto. Una vez que el subproceso tiene el bloqueo, puede comprobar el nuevo estado del objeto para ver si se ha alcanzado el estado necesario.
Exit Libera el bloqueo en un objeto . Esta acción también marca el final de una sección crítica protegida por el objeto bloqueado.

A partir de .NET Framework 4, hay dos conjuntos de sobrecargas para los Enter métodos y TryEnter . Un conjunto de sobrecargas tiene un ref parámetro (en C#) o ByRef (en Visual Basic) Boolean que se establece true atómicamente en si se adquiere el bloqueo, incluso si se produce una excepción al adquirir el bloqueo. Use estas sobrecargas si es fundamental liberar el bloqueo en todos los casos, incluso cuando los recursos que protege el bloqueo podrían no estar en un estado coherente.

El objeto lock

La clase Monitor consta de static (en C#) o Shared (en Visual Basic) métodos que operan en un objeto que controla el acceso a la sección crítica. La siguiente información se mantiene para cada objeto sincronizado:

  • Referencia al subproceso que contiene actualmente el bloqueo.

  • Referencia a una cola lista, que contiene los subprocesos que están listos para obtener el bloqueo.

  • Referencia a una cola en espera, que contiene los subprocesos que están esperando la notificación de un cambio en el estado del objeto bloqueado.

Monitor bloquea objetos (es decir, tipos de referencia), no tipos de valor. Aunque puede pasar un tipo de valor a Enter y Exit, se somete a una conversión boxing independiente para cada llamada. Puesto que cada llamada crea un objeto independiente, Enter nunca bloquea y el código al que supuestamente protege no está sincronizado realmente. Además, el objeto que se pasa a Exit es diferente del objeto que se pasa a Enter, por lo que Monitor produce la excepción SynchronizationLockException con el mensaje “El método de sincronización del objeto se ha llamado desde un bloque de códigos sin sincronizar.”.

El siguiente ejemplo ilustra este problema. Inicia diez tareas, cada una de los cuales solo se suspende durante 250 milisegundos. A continuación, cada tarea actualiza una variable de contador, nTasks, pensada para contar el número de tareas que realmente se iniciaron y ejecutaron. Dado que nTasks es una variable global que pueden actualizar varias tareas al mismo tiempo, se usa un monitor para protegerla frente a modificaciones simultáneas de varias tareas. Sin embargo, como muestra la salida del ejemplo, cada una de las tareas produce una excepción SynchronizationLockException.

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.

Cada tarea produce una excepción SynchronizationLockException porque la variable nTasks es objeto de a una conversión boxing antes de llamar al método Monitor.Enter en cada tarea. En otras palabras, a cada llamada al método se le pasa una variable independiente, que es independiente del resto. nTasks se vuelve a someter a una conversión boxing en la llamada al método Monitor.Exit. Una vez más, esto crea diez nuevas variables sometidas a conversión boxing, que son independientes entre sí, nTasks, y las diez variables creadas en la llamada al método Monitor.Enter y sometidas a una conversión boxing. A continuación, se produce la excepción porque el código está intentando liberar un bloqueo en una variable recién creada que no se ha bloqueado anteriormente.

Aunque puede aplicar una conversión boxing a una variable de tipo de valor antes de llamar a Enter y Exit, tal como se muestra en el siguiente ejemplo, y pasar el mismo objeto sometido a conversión boxing a ambos métodos, hacerlo no ofrece ninguna ventaja. Los cambios realizados en la variable sometida a conversión unboxing no se reflejan en la copia sometida a conversión boxing, y no hay ninguna forma de cambiar el valor de la copia de sometida a conversión boxing.

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.

Al seleccionar un objeto en el que se va a sincronizar, solo debe bloquear en objetos privados o internos. El bloqueo en objetos externos puede dar lugar a interbloqueos, ya que el código no relacionado podría elegir los mismos objetos en los que bloquearse con distintos fines.

Tenga en cuenta que puede sincronizar en un objeto en varios dominios de aplicación si el objeto usado para el bloqueo deriva de MarshalByRefObject.

Sección crítica

Use los Enter métodos y Exit para marcar el principio y el final de una sección crítica.

Nota

La funcionalidad proporcionada por los Enter métodos y Exit es idéntica a la proporcionada por la instrucción lock en C# y la instrucción SyncLock en Visual Basic, excepto que las construcciones del lenguaje encapsulan la Monitor.Enter(Object, Boolean) sobrecarga del método y el Monitor.Exit método en un try...finally bloquear para asegurarse de que se libera el monitor.

Si la sección crítica es un conjunto de instrucciones contiguas, el bloqueo adquirido por el Enter método garantiza que solo un único subproceso pueda ejecutar el código incluido con el objeto bloqueado. En este caso, se recomienda colocar ese código en un try bloque y colocar la llamada al Exit método en un finally bloque. Esto garantiza la liberación del bloqueo aunque se produzca una excepción. El siguiente fragmento de código ilustra este patrón.

// 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

Esta instalación se usa normalmente para sincronizar el acceso a un método estático o de instancia de una clase.

Si una sección crítica abarca un método completo, la instalación de bloqueo se puede lograr colocando System.Runtime.CompilerServices.MethodImplAttribute en el método y especificando el Synchronized valor en el constructor de System.Runtime.CompilerServices.MethodImplAttribute. Cuando se usa este atributo, no se necesitan las Enter llamadas de método y Exit . El fragmento de código siguiente ilustra este patrón:

[MethodImplAttribute(MethodImplOptions.Synchronized)]
void MethodToLock()
{
   // Method implementation.
}
<MethodImplAttribute(MethodImplOptions.Synchronized)>
Sub MethodToLock()
   ' Method implementation.
End Sub

Tenga en cuenta que el atributo hace que el subproceso actual mantenga el bloqueo hasta que el método devuelva; si el bloqueo se puede liberar antes, use la Monitor clase , la instrucción lock de C# o la instrucción SyncLock de Visual Basic dentro del método en lugar del atributo .

Aunque es posible que las Enter instrucciones y Exit bloqueen y liberen un objeto determinado para cruzar límites de miembro o clase o ambos, no se recomienda esta práctica.

Pulse, PulseAll y Wait

Una vez que un subproceso posee el bloqueo y ha entrado en la sección crítica que protege el bloqueo, puede llamar a los Monitor.Waitmétodos , Monitor.Pulsey Monitor.PulseAll .

Cuando el subproceso que contiene las llamadas Waitde bloqueo, se libera el bloqueo y el subproceso se agrega a la cola en espera del objeto sincronizado. El primer subproceso de la cola lista, si existe, adquiere el bloqueo y entra en la sección crítica. El subproceso al que se llama Wait se mueve de la cola en espera a la cola lista cuando el Monitor.PulseAll subproceso llama al Monitor.Pulse método o que contiene el bloqueo (que se va a mover, el subproceso debe estar al principio de la cola en espera). El Wait método devuelve cuando el subproceso que realiza la llamada vuelve a adquirir el bloqueo.

Cuando el subproceso que contiene el bloqueo llama Pulsea , el subproceso al encabezado de la cola en espera se mueve a la cola lista. La llamada al PulseAll método mueve todos los subprocesos de la cola en espera a la cola lista.

Monitores y identificadores de espera

Es importante tener en cuenta la distinción entre el uso de la clase y WaitHandle los Monitor objetos.

  • La Monitor clase es totalmente administrada, totalmente portátil y podría ser más eficaz en términos de requisitos de recursos del sistema operativo.

  • Los objetos WaitHandle representan objetos del sistema operativo que pueden esperar, que son útiles para la sincronización entre el código administrado y el no administrado y exponen algunas características avanzadas del sistema operativo, como la capacidad de esperar muchos objetos a la vez.

Propiedades

LockContentionCount

Obtiene el número de veces que ha habido contención al intentar tomar el bloqueo del monitor.

Métodos

Enter(Object)

Adquiere un bloqueo exclusivo en el objeto especificado.

Enter(Object, Boolean)

Adquiere un bloqueo exclusivo en el objeto especificado y establece de forma atómica un valor que indica si se realizó el bloqueo.

Exit(Object)

Libera un bloqueo exclusivo en el objeto especificado.

IsEntered(Object)

Determina si el subproceso actual mantiene el bloqueo en el objeto especificado.

Pulse(Object)

Notifica un cambio de estado del objeto bloqueado al subproceso que se encuentra en la cola de espera.

PulseAll(Object)

Notifica un cambio de estado del objeto a todos los subprocesos que se encuentran en espera.

TryEnter(Object)

Intenta adquirir un bloqueo exclusivo en el objeto especificado.

TryEnter(Object, Boolean)

Intenta adquirir un bloqueo exclusivo en el objeto especificado y establece de forma atómica un valor que indica si se realizó el bloqueo.

TryEnter(Object, Int32)

Intenta adquirir un bloqueo exclusivo en el objeto especificado durante el número de segundos especificado.

TryEnter(Object, Int32, Boolean)

Intenta, durante el número especificado de milisegundos, adquirir un bloqueo exclusivo en el objeto especificado y establece de forma atómica un valor que indica si se realizó el bloqueo.

TryEnter(Object, TimeSpan)

Intenta adquirir un bloqueo exclusivo en el objeto especificado durante el período de tiempo especificado.

TryEnter(Object, TimeSpan, Boolean)

Intenta, durante el periodo de tiempo indicado, adquirir un bloqueo exclusivo en el objeto especificado y establece de forma atómica un valor que indica si se realizó el bloqueo.

Wait(Object)

Libera el bloqueo en un objeto y bloquea el subproceso actual hasta que vuelve a adquirir el bloqueo.

Wait(Object, Int32)

Libera el bloqueo en un objeto y bloquea el subproceso actual hasta que vuelve a adquirir el bloqueo. Si transcurre el intervalo de tiempo de espera especificado, el subproceso entra en la cola de subprocesos listos.

Wait(Object, Int32, Boolean)

Libera el bloqueo en un objeto y bloquea el subproceso actual hasta que vuelve a adquirir el bloqueo. Si transcurre el intervalo de tiempo de espera especificado, el subproceso entra en la cola de subprocesos listos. Este método también especifica si el dominio de sincronización del contexto (si se trata de un contexto sincronizado) sale antes de la espera y vuelve a adquirir el bloqueo después.

Wait(Object, TimeSpan)

Libera el bloqueo en un objeto y bloquea el subproceso actual hasta que vuelve a adquirir el bloqueo. Si transcurre el intervalo de tiempo de espera especificado, el subproceso entra en la cola de subprocesos listos.

Wait(Object, TimeSpan, Boolean)

Libera el bloqueo en un objeto y bloquea el subproceso actual hasta que vuelve a adquirir el bloqueo. Si transcurre el intervalo de tiempo de espera especificado, el subproceso entra en la cola de subprocesos listos. De modo opcional, sale del dominio de sincronización del contexto sincronizado antes de la espera y vuelve a adquirir el dominio después.

Se aplica a

Seguridad para subprocesos

Este tipo es seguro para la ejecución de subprocesos.

Consulte también