Clase System.Threading.Monitor

En este artículo se proporcionan comentarios adicionales a la documentación de referencia de esta API.

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. Dado que la clase Monitor tiene afinidad de subproceso, el subproceso que adquirió un bloqueo debe liberar el bloqueo llamando al método Monitor.Exit.

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; 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 de String), 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 accede 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.

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 métodos (Shared en Visual Basic) que operan en un objeto que controla el acceso a la sección crítica. Se mantiene la siguiente información 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 esperan 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 Example1
{
    public static void Main()
    {
        int nTasks = 0;
        List<Task> tasks = new List<Task>();

        try
        {
            for (int ctr = 0; ctr < 10; ctr++)
                tasks.Add(Task.Run(() =>
                { // Instead of doing some work, just sleep.
                    Thread.Sleep(250);
                    // Increment the number of tasks.
                    Monitor.Enter(nTasks);
                    try
                    {
                        nTasks += 1;
                    }
                    finally
                    {
                        Monitor.Exit(nTasks);
                    }
                }));
            Task.WaitAll(tasks.ToArray());
            Console.WriteLine("{0} tasks started and executed.", nTasks);
        }
        catch (AggregateException e)
        {
            String msg = String.Empty;
            foreach (var ie in e.InnerExceptions)
            {
                Console.WriteLine("{0}", ie.GetType().Name);
                if (!msg.Contains(ie.Message))
                    msg += ie.Message + Environment.NewLine;
            }
            Console.WriteLine("\nException Message(s):");
            Console.WriteLine(msg);
        }
    }
}
// The example displays the following output:
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//
//    Exception Message(s):
//    Object synchronization method was called from an unsynchronized block of code.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Module Example3
    Public Sub Main()
        Dim nTasks As Integer = 0
        Dim tasks As New List(Of Task)()

        Try
            For ctr As Integer = 0 To 9
                tasks.Add(Task.Run(Sub()
                                       ' Instead of doing some work, just sleep.
                                       Thread.Sleep(250)
                                       ' Increment the number of tasks.
                                       Monitor.Enter(nTasks)
                                       Try
                                           nTasks += 1
                                       Finally
                                           Monitor.Exit(nTasks)
                                       End Try
                                   End Sub))
            Next
            Task.WaitAll(tasks.ToArray())
            Console.WriteLine("{0} tasks started and executed.", nTasks)
        Catch e As AggregateException
            Dim msg As String = String.Empty
            For Each ie In e.InnerExceptions
                Console.WriteLine("{0}", ie.GetType().Name)
                If Not msg.Contains(ie.Message) Then
                    msg += ie.Message + Environment.NewLine
                End If
            Next
            Console.WriteLine(vbCrLf + "Exception Message(s):")
            Console.WriteLine(msg)
        End Try
    End Sub
End Module
' The example displays the following output:
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'
'    Exception Message(s):
'    Object synchronization method was called from an unsynchronized block of code.

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 Example2
    Public Sub Main()
        Dim nTasks As Integer = 0
        Dim o As Object = nTasks
        Dim tasks As New List(Of Task)()

        Try
            For ctr As Integer = 0 To 9
                tasks.Add(Task.Run(Sub()
                                       ' Instead of doing some work, just sleep.
                                       Thread.Sleep(250)
                                       ' Increment the number of tasks.
                                       Monitor.Enter(o)
                                       Try
                                           nTasks += 1
                                       Finally
                                           Monitor.Exit(o)
                                       End Try
                                   End Sub))
            Next
            Task.WaitAll(tasks.ToArray())
            Console.WriteLine("{0} tasks started and executed.", nTasks)
        Catch e As AggregateException
            Dim msg As String = String.Empty
            For Each ie In e.InnerExceptions
                Console.WriteLine("{0}", ie.GetType().Name)
                If Not msg.Contains(ie.Message) Then
                    msg += ie.Message + Environment.NewLine
                End If
            Next
            Console.WriteLine(vbCrLf + "Exception Message(s):")
            Console.WriteLine(msg)
        End Try
    End Sub
End Module
' The example displays the following output:
'       10 tasks started and executed.

Al seleccionar un objeto en el que se va a sincronizar, solo se 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 para bloquearlos con fines diferentes.

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 métodos y Exit es idéntica a la proporcionada por la instrucción lock en C# y la instrucción SyncLock de Visual Basic, excepto que el lenguaje construye encapsular la Monitor.Enter(Object, Boolean) sobrecarga del método y el Monitor.Exit método en un try...Enterfinally bloque 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. En el fragmento de código siguiente se muestra 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 en System.Runtime.CompilerServices.MethodImplAttribute 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 llamadas al Enter 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 contenga 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 instrucciones Enter 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 escrito 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 las llamadas Pulsede bloqueo, el subproceso en el 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 puramente 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.

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 de 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 global.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example2
{
    public static void Main()
    {
        List<Task> tasks = new List<Task>();
        Random rnd = new Random();
        long total = 0;
        int n = 0;

        for (int taskCtr = 0; taskCtr < 10; taskCtr++)
            tasks.Add(Task.Run(() =>
            {
                int[] values = new int[10000];
                int taskTotal = 0;
                int taskN = 0;
                int ctr = 0;
                Monitor.Enter(rnd);
                // Generate 10,000 random integers
                for (ctr = 0; ctr < 10000; ctr++)
                    values[ctr] = rnd.Next(0, 1001);
                Monitor.Exit(rnd);
                taskN = ctr;
                foreach (var value in values)
                    taskTotal += value;

                Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
                                  Task.CurrentId, (taskTotal * 1.0) / taskN,
                                  taskN);
                Interlocked.Add(ref n, taskN);
                Interlocked.Add(ref total, taskTotal);
            }));
        try
        {
            Task.WaitAll(tasks.ToArray());
            Console.WriteLine("\nMean for all tasks: {0:N2} (N={1:N0})",
                              (total * 1.0) / n, n);
        }
        catch (AggregateException e)
        {
            foreach (var ie in e.InnerExceptions)
                Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message);
        }
    }
}
// The example displays output like the following:
//       Mean for task  1: 499.04 (N=10,000)
//       Mean for task  2: 500.42 (N=10,000)
//       Mean for task  3: 499.65 (N=10,000)
//       Mean for task  8: 502.59 (N=10,000)
//       Mean for task  5: 502.75 (N=10,000)
//       Mean for task  4: 494.88 (N=10,000)
//       Mean for task  7: 499.22 (N=10,000)
//       Mean for task 10: 496.45 (N=10,000)
//       Mean for task  6: 499.75 (N=10,000)
//       Mean for task  9: 502.79 (N=10,000)
//
//       Mean for all tasks: 499.75 (N=100,000)
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Module Example4
    Public Sub Main()
        Dim tasks As New List(Of Task)()
        Dim rnd As New Random()
        Dim total As Long = 0
        Dim n As Integer = 0

        For taskCtr As Integer = 0 To 9
            tasks.Add(Task.Run(Sub()
                                   Dim values(9999) As Integer
                                   Dim taskTotal As Integer = 0
                                   Dim taskN As Integer = 0
                                   Dim ctr As Integer = 0
                                   Monitor.Enter(rnd)
                                   ' Generate 10,000 random integers.
                                   For ctr = 0 To 9999
                                       values(ctr) = rnd.Next(0, 1001)
                                   Next
                                   Monitor.Exit(rnd)
                                   taskN = ctr
                                   For Each value In values
                                       taskTotal += value
                                   Next

                                   Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
                                                  Task.CurrentId, taskTotal / taskN,
                                                  taskN)
                                   Interlocked.Add(n, taskN)
                                   Interlocked.Add(total, taskTotal)
                               End Sub))
        Next

        Try
            Task.WaitAll(tasks.ToArray())
            Console.WriteLine()
            Console.WriteLine("Mean for all tasks: {0:N2} (N={1:N0})",
                           (total * 1.0) / n, n)
        Catch e As AggregateException
            For Each ie In e.InnerExceptions
                Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message)
            Next
        End Try
    End Sub
End Module
' The example displays output like the following:
'       Mean for task  1: 499.04 (N=10,000)
'       Mean for task  2: 500.42 (N=10,000)
'       Mean for task  3: 499.65 (N=10,000)
'       Mean for task  8: 502.59 (N=10,000)
'       Mean for task  5: 502.75 (N=10,000)
'       Mean for task  4: 494.88 (N=10,000)
'       Mean for task  7: 499.22 (N=10,000)
'       Mean for task 10: 496.45 (N=10,000)
'       Mean for task  6: 499.75 (N=10,000)
'       Mean for task  9: 502.79 (N=10,000)
'
'       Mean for all tasks: 499.75 (N=100,000)

Dado que se puede tener acceso a ellos desde cualquier tarea que se ejecute en un subproceso de grupo de subprocesos, también se debe sincronizar el acceso a las variables total y n . 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 del 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 decrementado 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 al subproceso principal que continúa 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 decrementado de un primer subproceso a la variable. Cuando el último subproceso de trabajo sin sincronizar disminuye el contador en cero, lo que indica que no es necesario tener acceso al recurso más subprocesos no asincrónicos, 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.