Condividi tramite


Classe System.Threading.Monitor

Questo articolo fornisce osservazioni supplementari alla documentazione di riferimento per questa API.

La Monitor classe consente di sincronizzare l'accesso a un'area di codice prendendo e rilasciando un blocco su un oggetto specifico chiamando i Monitor.Entermetodi , Monitor.TryEntere Monitor.Exit . I blocchi di oggetti consentono di limitare l'accesso a un blocco di codice, comunemente definito sezione critica. Mentre un thread è proprietario del blocco per un oggetto, nessun altro thread può acquisire tale blocco. È anche possibile usare la Monitor classe per assicurarsi che nessun altro thread possa accedere a una sezione del codice dell'applicazione eseguita dal proprietario del blocco, a meno che l'altro thread non esegua il codice usando un oggetto bloccato diverso. Poiché la classe Monitor ha affinità di thread, il thread che ha acquisito un blocco deve rilasciare il blocco chiamando il metodo Monitor.Exit.

Panoramica

Monitor include le funzionalità seguenti:

  • È associato a un oggetto su richiesta.
  • Non è associato, il che significa che può essere chiamato direttamente da qualsiasi contesto.
  • Impossibile creare un'istanza della Monitor classe. I metodi della Monitor classe sono tutti statici. Ogni metodo viene passato all'oggetto sincronizzato che controlla l'accesso alla sezione critica.

Nota

Usare la Monitor classe per bloccare oggetti diversi dalle stringhe, ovvero tipi di riferimento diversi da String, non tipi valore. Per informazioni dettagliate, vedere gli overload del Enter metodo e la sezione Oggetto lock più avanti in questo articolo.

Nella tabella seguente vengono descritte le azioni che possono essere eseguite dai thread che accedono agli oggetti sincronizzati:

Azione Descrizione
Enter, TryEnter Acquisisce un blocco per un oggetto . Questa azione contrassegna anche l'inizio di una sezione critica. Nessun altro thread può immettere la sezione critica a meno che non esegua le istruzioni nella sezione critica usando un oggetto bloccato diverso.
Wait Rilascia il blocco su un oggetto per consentire ad altri thread di bloccare e accedere all'oggetto. Il thread chiamante attende mentre un altro thread accede all'oggetto. I segnali di impulso vengono usati per notificare ai thread in attesa le modifiche apportate allo stato di un oggetto.
Pulse (segnale), PulseAll Invia un segnale a uno o più thread in attesa. Il segnale notifica a un thread in attesa che lo stato dell'oggetto bloccato sia stato modificato e che il proprietario del blocco sia pronto per rilasciare il blocco. Il thread in attesa viene inserito nella coda pronta dell'oggetto in modo che possa eventualmente ricevere il blocco per l'oggetto. Una volta che il thread ha il blocco, può controllare il nuovo stato dell'oggetto per verificare se lo stato richiesto è stato raggiunto.
Exit Rilascia il blocco su un oggetto . Questa azione contrassegna anche la fine di una sezione critica protetta dall'oggetto bloccato.

Esistono due set di overload per i Enter metodi e TryEnter . Un set di overload ha un ref parametro (in C#) o ByRef (in Visual Basic) Boolean impostato true in modo atomico su se il blocco viene acquisito, anche se viene generata un'eccezione durante l'acquisizione del blocco. Usare questi overload se è fondamentale rilasciare il blocco in tutti i casi, anche quando le risorse protette dal blocco potrebbero non trovarsi in uno stato coerente.

Oggetto lock

La classe Monitor è costituita da static metodi (Shared in Visual Basic) che operano su un oggetto che controlla l'accesso alla sezione critica. Per ogni oggetto sincronizzato vengono mantenute le informazioni seguenti:

  • Riferimento al thread che attualmente contiene il blocco.
  • Riferimento a una coda pronta, che contiene i thread pronti per ottenere il blocco.
  • Riferimento a una coda in attesa, che contiene i thread in attesa di notifica di una modifica nello stato dell'oggetto bloccato.

Monitor blocca gli oggetti, ovvero i tipi di riferimento, non i tipi di valore. Anche se è possibile passare un tipo di valore a Enter e Exit, ogni valore viene sottoposto a boxing separatamente per ogni chiamata. Poiché ogni chiamata crea un oggetto separato, Enter non si blocca mai e non sincronizza realmente il codice che dovrebbe proteggere. L'oggetto passato a Exit, inoltre, è diverso dall'oggetto passato a Enter, quindi Monitor genera un'eccezione SynchronizationLockException con il messaggio "Il metodo di sincronizzazione dell'oggetto è stato chiamato da un blocco di codice non sincronizzato".

L'esempio seguente illustra questo problema. Avvia dieci attività, ognuna delle quali rimane semplicemente inattiva per 250 millisecondi. Ogni attività aggiorna quindi una variabile del contatore, nTasks, in modo da contare il numero di attività effettivamente avviate ed eseguite. Poiché nTasks è una variabile globale che può essere aggiornata da più attività contemporaneamente, viene usato un oggetto monitor per proteggerla dalla modifica simultanea da parte di più attività. Tuttavia, come illustrato dall'output dell'esempio, ogni attività genera un'eccezione 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.

Ogni attività genera un'eccezione SynchronizationLockException, poiché la variabile nTasks viene sottoposta a boxing prima della chiamata al metodo Monitor.Enter in ogni attività. In altri termini, una variabile separata, indipendente dalle altre, viene passata a ogni chiamata al metodo. nTasks viene sottoposto di nuovo a boxing nella chiamata al metodo Monitor.Exit. Ancora una volta, ciò crea dieci nuove variabili di tipo boxed, indipendenti le une dalle altre, nTasks e le dieci variabili di tipo boxed create nella chiamata al metodo Monitor.Enter. L'eccezione viene generata, quindi, poiché il codice sta tentando di rilasciare un blocco su una variabile appena creata non bloccata in precedenza.

Anche se è possibile sottoporre a boxing una variabile di tipo valore prima di chiamare Enter e Exit, come illustrato nell'esempio seguente, e passare lo stesso oggetto di tipo boxed a entrambi i metodi, ciò non presenta alcun vantaggio. Le modifiche alla variabile di tipo unboxed non sono riflesse nella copia di tipo boxed e non è possibile modificare il valore della copia di tipo boxed.

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.

Quando si seleziona un oggetto in cui eseguire la sincronizzazione, è necessario bloccare solo gli oggetti privati o interni. Il blocco su oggetti esterni potrebbe comportare deadlock, perché il codice non correlato potrebbe scegliere gli stessi oggetti da bloccare per scopi diversi.

Si noti che è possibile eseguire la sincronizzazione su un oggetto in più domini applicazione se l'oggetto usato per il blocco deriva da MarshalByRefObject.

Sezione critica

Usare i Enter metodi e Exit per contrassegnare l'inizio e la fine di una sezione critica.

Nota

La funzionalità fornita dai Enter metodi e Exit è identica a quella fornita dall'istruzione lock in C# e dall'istruzione SyncLock in Visual Basic, ad eccezione del fatto che i costrutti del linguaggio eseguono il wrapping dell'overload Monitor.Enter(Object, Boolean) del metodo e del Monitor.Exit metodo in un ...tryfinally per assicurarsi che il monitoraggio venga rilasciato.

Se la sezione critica è un set di istruzioni contigue, il blocco acquisito dal Enter metodo garantisce che solo un singolo thread possa eseguire il codice racchiuso con l'oggetto bloccato. In questo caso, è consigliabile inserire il codice in un try blocco e inserire la chiamata al Exit metodo in un finally blocco. Ciò assicura che il blocco venga rilasciato anche se si verifica un'eccezione. Il frammento di codice seguente illustra questo modello.

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

Questa funzionalità viene in genere usata per sincronizzare l'accesso a un metodo statico o di istanza di una classe.

Se una sezione critica si estende su un intero metodo, la struttura di blocco può essere ottenuta inserendo sul System.Runtime.CompilerServices.MethodImplAttribute metodo e specificando il Synchronized valore nel costruttore di System.Runtime.CompilerServices.MethodImplAttribute. Quando si usa questo attributo, le chiamate al Enter metodo e Exit non sono necessarie. Il frammento di codice seguente illustra questo modello:

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

Si noti che l'attributo fa sì che il thread corrente contenga il blocco fino a quando il metodo non restituisce; se il blocco può essere rilasciato prima, usare la Monitor classe , l'istruzione di blocco C# o l'istruzione SyncLock di Visual Basic all'interno del metodo anziché l'attributo .

Sebbene sia possibile per le Enter istruzioni e Exit che bloccano e rilasciano un determinato oggetto per attraversare i limiti di membro o classe o entrambi, questa procedura non è consigliata.

Pulse, PulseAll e Wait

Dopo che un thread possiede il blocco e ha immesso la sezione critica che protegge il blocco, può chiamare i Monitor.Waitmetodi , Monitor.Pulsee Monitor.PulseAll .

Quando il thread che contiene il blocco chiama Wait, il blocco viene rilasciato e il thread viene aggiunto alla coda in attesa dell'oggetto sincronizzato. Il primo thread nella coda pronta, se presente, acquisisce il blocco e immette la sezione critica. Il thread chiamato Wait viene spostato dalla coda in attesa alla coda pronta quando Monitor.Pulse il metodo o Monitor.PulseAll viene chiamato dal thread che contiene il blocco (da spostare, il thread deve trovarsi all'inizio della coda in attesa). Il Wait metodo restituisce quando il thread chiamante riacquisi il blocco.

Quando il thread che contiene il blocco chiama Pulse, il thread all'inizio della coda in attesa viene spostato nella coda pronta. La chiamata al PulseAll metodo sposta tutti i thread dalla coda in attesa alla coda pronta.

Monitoraggi e handle di attesa

È importante notare la distinzione tra l'uso della classe e WaitHandle degli Monitor oggetti .

  • La Monitor classe è puramente gestita, completamente portabile e potrebbe essere più efficiente in termini di requisiti delle risorse del sistema operativo.
  • Gli oggetti WaitHandle rappresentano oggetti awaitable del sistema operativo, sono utili per la sincronizzazione tra codice gestito e non gestito ed espongono alcune funzionalità avanzate del sistema operativo, ad esempio la capacità di rimanere in attesa di più oggetti contemporaneamente.

Esempi

Nell'esempio seguente viene utilizzata la Monitor classe per sincronizzare l'accesso a una singola istanza di un generatore di numeri casuali rappresentato dalla Random classe . Nell'esempio vengono create dieci attività, ognuna delle quali viene eseguita in modo asincrono in un thread del pool di thread. Ogni attività genera 10.000 numeri casuali, calcola la media e aggiorna due variabili a livello di routine che mantengono un totale in esecuzione del numero di numeri casuali generati e la relativa somma. Dopo l'esecuzione di tutte le attività, questi due valori vengono quindi usati per calcolare la media complessiva.

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)

Poiché è possibile accedervi da qualsiasi attività in esecuzione in un thread del pool di thread, l'accesso alle variabili total e n deve anche essere sincronizzato. Il Interlocked.Add metodo viene utilizzato a questo scopo.

Nell'esempio seguente viene illustrato l'uso combinato della Monitor classe (implementata con il lock costrutto di linguaggio o SyncLock ), la Interlocked classe e la AutoResetEvent classe . Definisce due classi internal (in C#) o Friend (in Visual Basic), SyncResource e UnSyncResource, che forniscono rispettivamente l'accesso sincronizzato e non sincronizzato a una risorsa. Per assicurarsi che l'esempio illustri la differenza tra l'accesso sincronizzato e non sincronizzato (che può essere il caso se ogni chiamata al metodo viene completata rapidamente), il metodo include un ritardo casuale: per i thread la cui proprietà Thread.ManagedThreadId è pari, il metodo chiama Thread.Sleep per introdurre un ritardo di 2000 millisecondi. Si noti che, perché la classe SyncResource non è pubblica, nessuna parte del codice client acquisisce un blocco sulla risorsa sincronizzata: è la classe interna ad acquisire il blocco. Ciò impedisce l'acquisizione di un blocco su un oggetto pubblico da parte di codice dannoso.

using System;
using System.Threading;

internal class SyncResource
{
    // Use a monitor to enforce synchronization.
    public void Access()
    {
        lock(this) {
            Console.WriteLine("Starting synchronized resource access on thread #{0}",
                              Thread.CurrentThread.ManagedThreadId);
            if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
                Thread.Sleep(2000);

            Thread.Sleep(200);
            Console.WriteLine("Stopping synchronized resource access on thread #{0}",
                              Thread.CurrentThread.ManagedThreadId);
        }
    }
}

internal class UnSyncResource
{
    // Do not enforce synchronization.
    public void Access()
    {
        Console.WriteLine("Starting unsynchronized resource access on Thread #{0}",
                          Thread.CurrentThread.ManagedThreadId);
        if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
            Thread.Sleep(2000);

        Thread.Sleep(200);
        Console.WriteLine("Stopping unsynchronized resource access on thread #{0}",
                          Thread.CurrentThread.ManagedThreadId);
    }
}

public class App
{
    private static int numOps;
    private static AutoResetEvent opsAreDone = new AutoResetEvent(false);
    private static SyncResource SyncRes = new SyncResource();
    private static UnSyncResource UnSyncRes = new UnSyncResource();

   public static void Main()
   {
        // Set the number of synchronized calls.
        numOps = 5;
        for (int ctr = 0; ctr <= 4; ctr++)
            ThreadPool.QueueUserWorkItem(new WaitCallback(SyncUpdateResource));

        // Wait until this WaitHandle is signaled.
        opsAreDone.WaitOne();
        Console.WriteLine("\t\nAll synchronized operations have completed.\n");

        // Reset the count for unsynchronized calls.
        numOps = 5;
        for (int ctr = 0; ctr <= 4; ctr++)
            ThreadPool.QueueUserWorkItem(new WaitCallback(UnSyncUpdateResource));

        // Wait until this WaitHandle is signaled.
        opsAreDone.WaitOne();
        Console.WriteLine("\t\nAll unsynchronized thread operations have completed.\n");
   }

    static void SyncUpdateResource(Object state)
    {
        // Call the internal synchronized method.
        SyncRes.Access();

        // Ensure that only one thread can decrement the counter at a time.
        if (Interlocked.Decrement(ref numOps) == 0)
            // Announce to Main that in fact all thread calls are done.
            opsAreDone.Set();
    }

    static void UnSyncUpdateResource(Object state)
    {
        // Call the unsynchronized method.
        UnSyncRes.Access();

        // Ensure that only one thread can decrement the counter at a time.
        if (Interlocked.Decrement(ref numOps) == 0)
            // Announce to Main that in fact all thread calls are done.
            opsAreDone.Set();
    }
}
// The example displays output like the following:
//    Starting synchronized resource access on thread #6
//    Stopping synchronized resource access on thread #6
//    Starting synchronized resource access on thread #7
//    Stopping synchronized resource access on thread #7
//    Starting synchronized resource access on thread #3
//    Stopping synchronized resource access on thread #3
//    Starting synchronized resource access on thread #4
//    Stopping synchronized resource access on thread #4
//    Starting synchronized resource access on thread #5
//    Stopping synchronized resource access on thread #5
//
//    All synchronized operations have completed.
//
//    Starting unsynchronized resource access on Thread #7
//    Starting unsynchronized resource access on Thread #9
//    Starting unsynchronized resource access on Thread #10
//    Starting unsynchronized resource access on Thread #6
//    Starting unsynchronized resource access on Thread #3
//    Stopping unsynchronized resource access on thread #7
//    Stopping unsynchronized resource access on thread #9
//    Stopping unsynchronized resource access on thread #3
//    Stopping unsynchronized resource access on thread #10
//    Stopping unsynchronized resource access on thread #6
//
//    All unsynchronized thread operations have completed.
Imports System.Threading

Friend Class SyncResource
    ' Use a monitor to enforce synchronization.
    Public Sub Access()
        SyncLock Me
            Console.WriteLine("Starting synchronized resource access on thread #{0}",
                              Thread.CurrentThread.ManagedThreadId)
            If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
                Thread.Sleep(2000)
            End If
            Thread.Sleep(200)
            Console.WriteLine("Stopping synchronized resource access on thread #{0}",
                              Thread.CurrentThread.ManagedThreadId)
        End SyncLock
    End Sub
End Class

Friend Class UnSyncResource
    ' Do not enforce synchronization.
    Public Sub Access()
        Console.WriteLine("Starting unsynchronized resource access on Thread #{0}",
                          Thread.CurrentThread.ManagedThreadId)
        If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
            Thread.Sleep(2000)
        End If
        Thread.Sleep(200)
        Console.WriteLine("Stopping unsynchronized resource access on thread #{0}",
                          Thread.CurrentThread.ManagedThreadId)
    End Sub
End Class

Public Module App
    Private numOps As Integer
    Private opsAreDone As New AutoResetEvent(False)
    Private SyncRes As New SyncResource()
    Private UnSyncRes As New UnSyncResource()

    Public Sub Main()
        ' Set the number of synchronized calls.
        numOps = 5
        For ctr As Integer = 0 To 4
            ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf SyncUpdateResource))
        Next
        ' Wait until this WaitHandle is signaled.
        opsAreDone.WaitOne()
        Console.WriteLine(vbTab + Environment.NewLine + "All synchronized operations have completed.")
        Console.WriteLine()

        numOps = 5
        ' Reset the count for unsynchronized calls.
        For ctr As Integer = 0 To 4
            ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf UnSyncUpdateResource))
        Next

        ' Wait until this WaitHandle is signaled.
        opsAreDone.WaitOne()
        Console.WriteLine(vbTab + Environment.NewLine + "All unsynchronized thread operations have completed.")
    End Sub

    Sub SyncUpdateResource()
        ' Call the internal synchronized method.
        SyncRes.Access()

        ' Ensure that only one thread can decrement the counter at a time.
        If Interlocked.Decrement(numOps) = 0 Then
            ' Announce to Main that in fact all thread calls are done.
            opsAreDone.Set()
        End If
    End Sub

    Sub UnSyncUpdateResource()
        ' Call the unsynchronized method.
        UnSyncRes.Access()

        ' Ensure that only one thread can decrement the counter at a time.
        If Interlocked.Decrement(numOps) = 0 Then
            ' Announce to Main that in fact all thread calls are done.
            opsAreDone.Set()
        End If
    End Sub
End Module
' The example displays output like the following:
'    Starting synchronized resource access on thread #6
'    Stopping synchronized resource access on thread #6
'    Starting synchronized resource access on thread #7
'    Stopping synchronized resource access on thread #7
'    Starting synchronized resource access on thread #3
'    Stopping synchronized resource access on thread #3
'    Starting synchronized resource access on thread #4
'    Stopping synchronized resource access on thread #4
'    Starting synchronized resource access on thread #5
'    Stopping synchronized resource access on thread #5
'
'    All synchronized operations have completed.
'
'    Starting unsynchronized resource access on Thread #7
'    Starting unsynchronized resource access on Thread #9
'    Starting unsynchronized resource access on Thread #10
'    Starting unsynchronized resource access on Thread #6
'    Starting unsynchronized resource access on Thread #3
'    Stopping unsynchronized resource access on thread #7
'    Stopping unsynchronized resource access on thread #9
'    Stopping unsynchronized resource access on thread #3
'    Stopping unsynchronized resource access on thread #10
'    Stopping unsynchronized resource access on thread #6
'
'    All unsynchronized thread operations have completed.

L'esempio definisce una variabile, numOps, che definisce il numero di thread che proverà ad accedere alla risorsa. Il thread dell'applicazione chiama per cinque volte ciascun metodo ThreadPool.QueueUserWorkItem(WaitCallback) per l'accesso sincronizzato e non sincronizzato. Il metodo ThreadPool.QueueUserWorkItem(WaitCallback) presenta un solo parametro, un delegato che non accetta parametri e non restituisce alcun valore. Per l'accesso sincronizzato, richiama il metodo SyncUpdateResource; per l'accesso non sincronizzato, richiama il metodo UnSyncUpdateResource. Dopo ogni set di chiamate al metodo, il thread dell'applicazione chiama il metodo AutoResetEvent.WaitOne in modo che blocchi fino a quando l'istanza AutoResetEvent non viene segnalata.

Ogni chiamata al metodo SyncUpdateResource chiama il metodo interno SyncResource.Access e quindi chiama il metodo Interlocked.Decrement per decrementare il contatore numOps. Il Interlocked.Decrement metodo viene usato per decrementare il contatore, perché in caso contrario non è possibile essere certi che un secondo thread acceda al valore prima che il valore decrementato di un primo thread sia stato archiviato nella variabile. Quando l'ultimo thread di lavoro sincronizzato decrementa il contatore su zero, a indicare che tutti i thread sincronizzati hanno completato l'accesso alla risorsa, il metodo chiama il SyncUpdateResourceEventWaitHandle.Set metodo , che segnala al thread principale di continuare l'esecuzione.

Ogni chiamata al metodo UnSyncUpdateResource chiama il metodo interno UnSyncResource.Access e quindi chiama il metodo Interlocked.Decrement per decrementare il contatore numOps. Ancora una volta, il Interlocked.Decrement metodo viene usato per decrementare il contatore per assicurarsi che un secondo thread non acceda al valore prima che alla variabile sia stato assegnato il valore decrementato di un primo thread. Quando l'ultimo thread di lavoro non sincronizzato decrementa il contatore su zero, a indicare che non è necessario che i thread non sincronizzati accedano alla risorsa, il UnSyncUpdateResource metodo chiama il EventWaitHandle.Set metodo , che segnala al thread principale di continuare l'esecuzione.

Come mostra il risultato dell'esempio, l'accesso sincronizzato garantisce che il thread di chiamata esca dalla risorsa protetta prima che un altro thread possa accedervi; ogni thread attende il suo predecessore. D'altra parte, senza il blocco, il metodo UnSyncResource.Access viene chiamato nell'ordine in cui i thread lo raggiungono.