Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
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 métodos Monitor.Enter, Monitor.TryEnter y 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 liberarlo mediante una llamada 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 hilos 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 más hilos que están en espera. La señal notifica a un hilo en espera que el estado del objeto bloqueado ha cambiado y el propietario del bloqueo está dispuesto a liberarlo. 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 parámetro ref
(en C#) o ByRef
(en Visual Basic) Boolean que se establece atómicamente en true
si se adquiere el bloqueo, incluso si se lanza una excepción al adquirir el bloqueo. Usa estas sobrecargas si es fundamental liberar el bloqueo en todos los casos, aun cuando los recursos que protege el bloqueo podrían no estar en un estado coherente.
El objeto de bloqueo
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 hilo que actualmente tiene el bloqueo.
- Referencia a una cola de listos, que contiene los hilos que están preparados 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 de un 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. Dado que cada llamada crea un objeto independiente, Enter nunca bloquea y el código que se supone que protege no está realmente sincronizado. Además, el objeto pasado a Exit es diferente del objeto pasado a Enter, por lo que Monitor produce SynchronizationLockException una excepción con el mensaje "Se llamó al método de sincronización de objetos desde un bloque de código no asincrónico".
El siguiente ejemplo ilustra este problema. Inicia diez tareas, cada una de las cuales solo duerme durante 250 milisegundos. A continuación, cada tarea actualiza una variable de contador, , nTasks
que está pensada para contar el número de tareas que realmente se iniciaron y ejecutaron. Dado nTasks
que es una variable global que varias tareas pueden actualizar simultáneamente, se usa un monitor para protegerlo de la modificación simultánea de varias tareas. Sin embargo, como se muestra en la salida del ejemplo, cada una de las tareas lanza una SynchronizationLockException excepción.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example1
{
public static void Main()
{
int nTasks = 0;
List<Task> tasks = new List<Task>();
try
{
for (int ctr = 0; ctr < 10; ctr++)
tasks.Add(Task.Run(() =>
{ // Instead of doing some work, just sleep.
Thread.Sleep(250);
// Increment the number of tasks.
Monitor.Enter(nTasks);
try
{
nTasks += 1;
}
finally
{
Monitor.Exit(nTasks);
}
}));
Task.WaitAll(tasks.ToArray());
Console.WriteLine($"{nTasks} tasks started and executed.");
}
catch (AggregateException e)
{
String msg = String.Empty;
foreach (var ie in e.InnerExceptions)
{
Console.WriteLine($"{ie.GetType().Name}");
if (!msg.Contains(ie.Message))
msg += ie.Message + Environment.NewLine;
}
Console.WriteLine("\nException Message(s):");
Console.WriteLine(msg);
}
}
}
// The example displays the following output:
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
//
// Exception Message(s):
// Object synchronization method was called from an unsynchronized block of code.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example3
Public Sub Main()
Dim nTasks As Integer = 0
Dim tasks As New List(Of Task)()
Try
For ctr As Integer = 0 To 9
tasks.Add(Task.Run(Sub()
' Instead of doing some work, just sleep.
Thread.Sleep(250)
' Increment the number of tasks.
Monitor.Enter(nTasks)
Try
nTasks += 1
Finally
Monitor.Exit(nTasks)
End Try
End Sub))
Next
Task.WaitAll(tasks.ToArray())
Console.WriteLine("{0} tasks started and executed.", nTasks)
Catch e As AggregateException
Dim msg As String = String.Empty
For Each ie In e.InnerExceptions
Console.WriteLine("{0}", ie.GetType().Name)
If Not msg.Contains(ie.Message) Then
msg += ie.Message + Environment.NewLine
End If
Next
Console.WriteLine(vbCrLf + "Exception Message(s):")
Console.WriteLine(msg)
End Try
End Sub
End Module
' The example displays the following output:
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
'
' Exception Message(s):
' Object synchronization method was called from an unsynchronized block of code.
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 que es independiente de las demás. 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 encapsuladas, que son independientes entre sí, nTasks
y las diez variables encapsuladas creadas en la llamada al método Monitor.Enter. Se lanza la excepción porque el código está intentando liberar un bloqueo en una variable recién creada que no había sido bloqueada anteriormente.
Aunque puede encapsular una variable de tipo de valor antes de llamar a Enter y Exit, como se muestra en el ejemplo siguiente, y pasar el mismo objeto encapsulado a ambos métodos, no hay ninguna ventaja en hacerlo. Los cambios en la variable no encapsulada no se reflejan en la copia encapsulada, y no hay forma de cambiar el valor de la copia encapsulada.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
int nTasks = 0;
object o = nTasks;
List<Task> tasks = new List<Task>();
try {
for (int ctr = 0; ctr < 10; ctr++)
tasks.Add(Task.Run( () => { // Instead of doing some work, just sleep.
Thread.Sleep(250);
// Increment the number of tasks.
Monitor.Enter(o);
try {
nTasks++;
}
finally {
Monitor.Exit(o);
}
} ));
Task.WaitAll(tasks.ToArray());
Console.WriteLine($"{nTasks} tasks started and executed.");
}
catch (AggregateException e) {
String msg = String.Empty;
foreach (var ie in e.InnerExceptions) {
Console.WriteLine($"{ie.GetType().Name}");
if (! msg.Contains(ie.Message))
msg += ie.Message + Environment.NewLine;
}
Console.WriteLine("\nException Message(s):");
Console.WriteLine(msg);
}
}
}
// The example displays the following output:
// 10 tasks started and executed.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example2
Public Sub Main()
Dim nTasks As Integer = 0
Dim o As Object = nTasks
Dim tasks As New List(Of Task)()
Try
For ctr As Integer = 0 To 9
tasks.Add(Task.Run(Sub()
' Instead of doing some work, just sleep.
Thread.Sleep(250)
' Increment the number of tasks.
Monitor.Enter(o)
Try
nTasks += 1
Finally
Monitor.Exit(o)
End Try
End Sub))
Next
Task.WaitAll(tasks.ToArray())
Console.WriteLine("{0} tasks started and executed.", nTasks)
Catch e As AggregateException
Dim msg As String = String.Empty
For Each ie In e.InnerExceptions
Console.WriteLine("{0}", ie.GetType().Name)
If Not msg.Contains(ie.Message) Then
msg += ie.Message + Environment.NewLine
End If
Next
Console.WriteLine(vbCrLf + "Exception Message(s):")
Console.WriteLine(msg)
End Try
End Sub
End Module
' The example displays the following output:
' 10 tasks started and executed.
Al seleccionar un objeto para sincronizar, debes bloquear solo 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 un objeto en varios dominios de aplicación si el objeto bloqueador 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 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
...finally
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 que el bloqueo se libere incluso si se produce 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, se puede lograr la instalación de bloqueo colocando el System.Runtime.CompilerServices.MethodImplAttribute en el método y especificando el valor de Synchronized 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 entrado en la sección crítica que protege el bloqueo, puede llamar a los métodos Monitor.Wait, Monitor.Pulse y Monitor.PulseAll.
Cuando el subproceso que sostiene el bloqueo llama a Wait, se libera el candado y el subproceso se agrega a la cola de 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 hilo que llamó a Wait se mueve de la cola de espera a la cola de listos cuando el hilo que posee el bloqueo llama a cualquiera de los métodos Monitor.Pulse o Monitor.PulseAll (para moverse, el hilo debe estar al frente de la cola de espera). El método Wait regresa cuando el subproceso que realiza la llamada vuelve a adquirir el bloqueo.
Cuando el subproceso que tiene el bloqueo llama a Pulse, el subproceso al frente de la cola de espera se mueve a la cola de listos. La llamada al método PulseAll mueve todos los hilos de la cola de espera a la cola preparada.
Monitores y identificadores de espera
Es importante tener en cuenta la distinción entre el uso de la Monitor clase y los WaitHandle 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 Monitor para sincronizar el acceso a una sola instancia de un generador de números aleatorios representado por la clase Random. En el ejemplo se crean diez tareas, cada una de las cuales se ejecuta de forma asincrónica en un hilo del grupo de hilos. 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: {(total * 1.0) / n:N2} (N={n:N0})");
}
catch (AggregateException e)
{
foreach (var ie in e.InnerExceptions)
Console.WriteLine($"{ie.GetType().Name}: {ie.Message}");
}
}
}
// The example displays output like the following:
// Mean for task 1: 499.04 (N=10,000)
// Mean for task 2: 500.42 (N=10,000)
// Mean for task 3: 499.65 (N=10,000)
// Mean for task 8: 502.59 (N=10,000)
// Mean for task 5: 502.75 (N=10,000)
// Mean for task 4: 494.88 (N=10,000)
// Mean for task 7: 499.22 (N=10,000)
// Mean for task 10: 496.45 (N=10,000)
// Mean for task 6: 499.75 (N=10,000)
// Mean for task 9: 502.79 (N=10,000)
//
// Mean for all tasks: 499.75 (N=100,000)
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example4
Public Sub Main()
Dim tasks As New List(Of Task)()
Dim rnd As New Random()
Dim total As Long = 0
Dim n As Integer = 0
For taskCtr As Integer = 0 To 9
tasks.Add(Task.Run(Sub()
Dim values(9999) As Integer
Dim taskTotal As Integer = 0
Dim taskN As Integer = 0
Dim ctr As Integer = 0
Monitor.Enter(rnd)
' Generate 10,000 random integers.
For ctr = 0 To 9999
values(ctr) = rnd.Next(0, 1001)
Next
Monitor.Exit(rnd)
taskN = ctr
For Each value In values
taskTotal += value
Next
Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
Task.CurrentId, taskTotal / taskN,
taskN)
Interlocked.Add(n, taskN)
Interlocked.Add(total, taskTotal)
End Sub))
Next
Try
Task.WaitAll(tasks.ToArray())
Console.WriteLine()
Console.WriteLine("Mean for all tasks: {0:N2} (N={1:N0})",
(total * 1.0) / n, n)
Catch e As AggregateException
For Each ie In e.InnerExceptions
Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message)
Next
End Try
End Sub
End Module
' The example displays output like the following:
' Mean for task 1: 499.04 (N=10,000)
' Mean for task 2: 500.42 (N=10,000)
' Mean for task 3: 499.65 (N=10,000)
' Mean for task 8: 502.59 (N=10,000)
' Mean for task 5: 502.75 (N=10,000)
' Mean for task 4: 494.88 (N=10,000)
' Mean for task 7: 499.22 (N=10,000)
' Mean for task 10: 496.45 (N=10,000)
' Mean for task 6: 499.75 (N=10,000)
' Mean for task 9: 502.79 (N=10,000)
'
' Mean for all tasks: 499.75 (N=100,000)
Dado que se puede acceder a ellos desde cualquier tarea que se ejecute en un hilo del grupo de hilos, 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 clase Monitor (implementada con el constructo del lenguaje lock
o SyncLock
), la clase Interlocked y la clase AutoResetEvent. Define dos internal
clases Friend
(en C#) o SyncResource
(en Visual Basic) y UnSyncResource
, que respectivamente proporcionan acceso sincronizado y no asincrónico a un recurso. Para asegurarse de que el ejemplo muestra la diferencia entre el acceso sincronizado y no asincrónico (que podría ser el caso si cada llamada al método se completa rápidamente), el método incluye un retraso aleatorio: para subprocesos cuya Thread.ManagedThreadId propiedad es uniforme, el método llama Thread.Sleep a para introducir un retraso de 2000 milisegundos. Tenga en cuenta que, dado que la clase SyncResource
no es pública, ninguno de los códigos del cliente adquiere un bloqueo sobre el recurso sincronizado; es la propia clase interna la que toma el bloqueo. Esto impide que el código malintencionado tome 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 #{Thread.CurrentThread.ManagedThreadId}");
if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
Thread.Sleep(2000);
Thread.Sleep(200);
Console.WriteLine($"Stopping synchronized resource access on thread #{Thread.CurrentThread.ManagedThreadId}");
}
}
}
internal class UnSyncResource
{
// Do not enforce synchronization.
public void Access()
{
Console.WriteLine($"Starting unsynchronized resource access on Thread #{Thread.CurrentThread.ManagedThreadId}");
if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
Thread.Sleep(2000);
Thread.Sleep(200);
Console.WriteLine($"Stopping unsynchronized resource access on thread #{Thread.CurrentThread.ManagedThreadId}");
}
}
public class App
{
private static int numOps;
private static AutoResetEvent opsAreDone = new AutoResetEvent(false);
private static SyncResource SyncRes = new SyncResource();
private static UnSyncResource UnSyncRes = new UnSyncResource();
public static void Main()
{
// Set the number of synchronized calls.
numOps = 5;
for (int ctr = 0; ctr <= 4; ctr++)
ThreadPool.QueueUserWorkItem(new WaitCallback(SyncUpdateResource));
// Wait until this WaitHandle is signaled.
opsAreDone.WaitOne();
Console.WriteLine("\t\nAll synchronized operations have completed.\n");
// Reset the count for unsynchronized calls.
numOps = 5;
for (int ctr = 0; ctr <= 4; ctr++)
ThreadPool.QueueUserWorkItem(new WaitCallback(UnSyncUpdateResource));
// Wait until this WaitHandle is signaled.
opsAreDone.WaitOne();
Console.WriteLine("\t\nAll unsynchronized thread operations have completed.\n");
}
static void SyncUpdateResource(Object state)
{
// Call the internal synchronized method.
SyncRes.Access();
// Ensure that only one thread can decrement the counter at a time.
if (Interlocked.Decrement(ref numOps) == 0)
// Announce to Main that in fact all thread calls are done.
opsAreDone.Set();
}
static void UnSyncUpdateResource(Object state)
{
// Call the unsynchronized method.
UnSyncRes.Access();
// Ensure that only one thread can decrement the counter at a time.
if (Interlocked.Decrement(ref numOps) == 0)
// Announce to Main that in fact all thread calls are done.
opsAreDone.Set();
}
}
// The example displays output like the following:
// Starting synchronized resource access on thread #6
// Stopping synchronized resource access on thread #6
// Starting synchronized resource access on thread #7
// Stopping synchronized resource access on thread #7
// Starting synchronized resource access on thread #3
// Stopping synchronized resource access on thread #3
// Starting synchronized resource access on thread #4
// Stopping synchronized resource access on thread #4
// Starting synchronized resource access on thread #5
// Stopping synchronized resource access on thread #5
//
// All synchronized operations have completed.
//
// Starting unsynchronized resource access on Thread #7
// Starting unsynchronized resource access on Thread #9
// Starting unsynchronized resource access on Thread #10
// Starting unsynchronized resource access on Thread #6
// Starting unsynchronized resource access on Thread #3
// Stopping unsynchronized resource access on thread #7
// Stopping unsynchronized resource access on thread #9
// Stopping unsynchronized resource access on thread #3
// Stopping unsynchronized resource access on thread #10
// Stopping unsynchronized resource access on thread #6
//
// All unsynchronized thread operations have completed.
Imports System.Threading
Friend Class SyncResource
' Use a monitor to enforce synchronization.
Public Sub Access()
SyncLock Me
Console.WriteLine("Starting synchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId)
If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
Thread.Sleep(2000)
End If
Thread.Sleep(200)
Console.WriteLine("Stopping synchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId)
End SyncLock
End Sub
End Class
Friend Class UnSyncResource
' Do not enforce synchronization.
Public Sub Access()
Console.WriteLine("Starting unsynchronized resource access on Thread #{0}",
Thread.CurrentThread.ManagedThreadId)
If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
Thread.Sleep(2000)
End If
Thread.Sleep(200)
Console.WriteLine("Stopping unsynchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId)
End Sub
End Class
Public Module App
Private numOps As Integer
Private opsAreDone As New AutoResetEvent(False)
Private SyncRes As New SyncResource()
Private UnSyncRes As New UnSyncResource()
Public Sub Main()
' Set the number of synchronized calls.
numOps = 5
For ctr As Integer = 0 To 4
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf SyncUpdateResource))
Next
' Wait until this WaitHandle is signaled.
opsAreDone.WaitOne()
Console.WriteLine(vbTab + Environment.NewLine + "All synchronized operations have completed.")
Console.WriteLine()
numOps = 5
' Reset the count for unsynchronized calls.
For ctr As Integer = 0 To 4
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf UnSyncUpdateResource))
Next
' Wait until this WaitHandle is signaled.
opsAreDone.WaitOne()
Console.WriteLine(vbTab + Environment.NewLine + "All unsynchronized thread operations have completed.")
End Sub
Sub SyncUpdateResource()
' Call the internal synchronized method.
SyncRes.Access()
' Ensure that only one thread can decrement the counter at a time.
If Interlocked.Decrement(numOps) = 0 Then
' Announce to Main that in fact all thread calls are done.
opsAreDone.Set()
End If
End Sub
Sub UnSyncUpdateResource()
' Call the unsynchronized method.
UnSyncRes.Access()
' Ensure that only one thread can decrement the counter at a time.
If Interlocked.Decrement(numOps) = 0 Then
' Announce to Main that in fact all thread calls are done.
opsAreDone.Set()
End If
End Sub
End Module
' The example displays output like the following:
' Starting synchronized resource access on thread #6
' Stopping synchronized resource access on thread #6
' Starting synchronized resource access on thread #7
' Stopping synchronized resource access on thread #7
' Starting synchronized resource access on thread #3
' Stopping synchronized resource access on thread #3
' Starting synchronized resource access on thread #4
' Stopping synchronized resource access on thread #4
' Starting synchronized resource access on thread #5
' Stopping synchronized resource access on thread #5
'
' All synchronized operations have completed.
'
' Starting unsynchronized resource access on Thread #7
' Starting unsynchronized resource access on Thread #9
' Starting unsynchronized resource access on Thread #10
' Starting unsynchronized resource access on Thread #6
' Starting unsynchronized resource access on Thread #3
' Stopping unsynchronized resource access on thread #7
' Stopping unsynchronized resource access on thread #9
' Stopping unsynchronized resource access on thread #3
' Stopping unsynchronized resource access on thread #10
' Stopping unsynchronized resource access on thread #6
'
' All unsynchronized thread operations have completed.
En el ejemplo se define una variable, numOps
, que define el número de subprocesos que intentarán acceder al recurso. El subproceso de aplicación llama al método ThreadPool.QueueUserWorkItem(WaitCallback) cinco veces para acceso sincronizado y cinco veces para acceso no sincronizado. El ThreadPool.QueueUserWorkItem(WaitCallback) método tiene un único parámetro, un delegado que no acepta parámetros y no devuelve ningún valor. Para el acceso sincronizado, invoca el SyncUpdateResource
método ; para el acceso no sincronizado, invoca el UnSyncUpdateResource
método . Después de cada conjunto de llamadas de método, el hilo de la aplicación llama al método AutoResetEvent.WaitOne, de modo que queda bloqueado hasta que la instancia AutoResetEvent recibe una señal.
Cada llamada al SyncUpdateResource
método llama al método interno SyncResource.Access
y, a continuación, llama al Interlocked.Decrement método para disminuir el numOps
contador. 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 UnSyncUpdateResource
método llama al método interno UnSyncResource.Access
y, a continuación, llama al Interlocked.Decrement método para disminuir el numOps
contador. 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 reduce el contador a cero, lo que indica que ningún otro subproceso sin sincronizar necesita acceder al recurso, el método UnSyncUpdateResource
llama al método EventWaitHandle.Set, que señala al subproceso principal que continúe con la ejecución.
Como se muestra en la salida del ejemplo, el acceso sincronizado garantiza que el subproceso que llama salga del recurso protegido antes de que otro subproceso pueda acceder a él; cada subproceso espera 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.