Monitor Classe

Definizione

Fornisce un meccanismo che sincronizza l'accesso agli oggetti.

public ref class Monitor abstract sealed
public ref class Monitor sealed
public static class Monitor
public sealed class Monitor
[System.Runtime.InteropServices.ComVisible(true)]
public static class Monitor
type Monitor = class
[<System.Runtime.InteropServices.ComVisible(true)>]
type Monitor = class
Public Class Monitor
Public NotInheritable Class Monitor
Ereditarietà
Monitor
Attributi

Esempio

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 Example
{
   public static void Main()
   {
      List<Task> tasks = new List<Task>();
      Random rnd = new Random();
      long total = 0;
      int n = 0;
      
      for (int taskCtr = 0; taskCtr < 10; taskCtr++)
         tasks.Add(Task.Run( () => {  int[] values = new int[10000];
                                      int taskTotal = 0;
                                      int taskN = 0;
                                      int ctr = 0;
                                      Monitor.Enter(rnd);
                                         // Generate 10,000 random integers
                                         for (ctr = 0; ctr < 10000; ctr++)
                                            values[ctr] = rnd.Next(0, 1001);
                                      Monitor.Exit(rnd);
                                      taskN = ctr;
                                      foreach (var value in values)
                                         taskTotal += value;

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

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

      For taskCtr As Integer = 0 To 9
         tasks.Add(Task.Run( Sub()
                                Dim values(9999) As Integer
                                Dim taskTotal As Integer = 0
                                Dim taskN As Integer = 0
                                Dim ctr As Integer = 0
                                Monitor.Enter(rnd)
                                   ' Generate 10,000 random integers.
                                    For ctr = 0 To 9999
                                       values(ctr) = rnd.Next(0, 1001)
                                    Next
                                Monitor.Exit(rnd)
                                taskN = ctr
                                For Each value in values
                                   taskTotal += value
                                Next
                                    
                                Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
                                                  Task.CurrentId, taskTotal/taskN,
                                                  taskN)
                                Interlocked.Add(n, taskN)
                                Interlocked.Add(total, taskTotal)
                             End Sub ))
      Next
      
      Try
         Task.WaitAll(tasks.ToArray())
         Console.WriteLine()
         Console.WriteLine("Mean for all tasks: {0:N2} (N={1:N0})",
                           (total * 1.0)/n, n)
      Catch e As AggregateException
         For Each ie In e.InnerExceptions
            Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message)
         Next
      End Try
   End Sub
End Module
' The example displays output like the following:
'       Mean for task  1: 499.04 (N=10,000)
'       Mean for task  2: 500.42 (N=10,000)
'       Mean for task  3: 499.65 (N=10,000)
'       Mean for task  8: 502.59 (N=10,000)
'       Mean for task  5: 502.75 (N=10,000)
'       Mean for task  4: 494.88 (N=10,000)
'       Mean for task  7: 499.22 (N=10,000)
'       Mean for task 10: 496.45 (N=10,000)
'       Mean for task  6: 499.75 (N=10,000)
'       Mean for task  9: 502.79 (N=10,000)
'
'       Mean for all tasks: 499.75 (N=100,000)

Poiché è possibile accedervi da qualsiasi attività in esecuzione in un thread del pool di thread, è necessario sincronizzare anche l'accesso alle variabilitotal.n Il Interlocked.Add metodo viene usato 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 nella variabile sia stato archiviato il valore decrementato di un primo thread. 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 SyncUpdateResource metodo chiama il EventWaitHandle.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. Anche in questo caso, il Interlocked.Decrement metodo viene usato per decrementare il contatore per garantire 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 accedere alla risorsa più thread non sincronizzati, il UnSyncUpdateResource metodo chiama il EventWaitHandle.Set metodo , che segnala al thread principale di continuare l'esecuzione.

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

Commenti

La Monitor classe consente di sincronizzare l'accesso a un'area di codice accettando 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.

Contenuto dell'articolo:

La classe Monitor: panoramica
Oggetto lock
Sezione critica
Pulse, PulseAll e Wait
Monitoraggi e handle di attesa

La classe Monitor: panoramica

Monitor include le seguenti funzionalità:

  • È 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 da stringhe ( ovvero tipi 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 pulse 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. Dopo che il thread ha il blocco, può controllare il nuovo stato dell'oggetto per verificare se è stato raggiunto lo stato richiesto.
Exit Rilascia il blocco su un oggetto . Questa azione contrassegna anche la fine di una sezione critica protetta dall'oggetto bloccato.

A partire da .NET Framework 4, sono disponibili 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 in modo atomico su true 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 (in C#) o 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 di 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 Example
{
   public static void Main()
   {

      int nTasks = 0;
      List<Task> tasks = new List<Task>();

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

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

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

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

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

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

Si noti che è possibile sincronizzare in 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 metodi e Exit è identica a quella fornita dall'istruzione Enterlock in C# e dall'istruzione SyncLock in Visual Basic, ad eccezione del fatto che il linguaggio costruisce il wrapping Monitor.Enter(Object, Boolean) dell'overload del metodo e il Monitor.Exit metodo in un try...finally blocca 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 struttura 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 inserendolo System.Runtime.CompilerServices.MethodImplAttribute nel 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 determina che il thread corrente mantiene 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 Visual Basic SyncLock all'interno del metodo anziché l'attributo.

Anche se è possibile che le Enter istruzioni e Exit che blocchino 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 il blocco protegge, può chiamare i Monitor.Waitmetodi , Monitor.Pulsee Monitor.PulseAll .

Quando il thread che contiene le chiamate Waitdi blocco , 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 essere alla testa della coda in attesa). Il Wait metodo restituisce quando il thread chiamante riacquisi il blocco.

Quando il thread che contiene le chiamate Pulsedi blocco , il thread alla testa 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.

Proprietà

LockContentionCount

Ottiene il numero di volte in cui si è verificata una contesa durante il tentativo di acquisire il blocco del monitoraggio.

Metodi

Enter(Object)

Acquisisce un blocco esclusivo sull'oggetto specificato.

Enter(Object, Boolean)

Acquisisce un blocco esclusivo sull'oggetto specificato e imposta atomicamente un valore che indica se il blocco è stato ottenuto.

Exit(Object)

Viene rilasciato un blocco esclusivo sull'oggetto specificato.

IsEntered(Object)

Determina se il thread corrente specificato contiene il blocco sull'oggetto specificato.

Pulse(Object)

Consente di notificare a un thread della coda di attesa che lo stato dell'oggetto bloccato è stato modificato.

PulseAll(Object)

Notifica a tutti i thread in attesa che lo stato dell'oggetto è stato modificato.

TryEnter(Object)

Prova ad acquisire un blocco esclusivo sull'oggetto specificato.

TryEnter(Object, Boolean)

Prova ad acquisire un blocco esclusivo sull'oggetto specificato e imposta atomicamente un valore che indica se il blocco è stato ottenuto.

TryEnter(Object, Int32)

Viene eseguito, per un numero specificato di millisecondi, il tentativo di acquisire un blocco esclusivo sull'oggetto specificato.

TryEnter(Object, Int32, Boolean)

Prova ad acquisire, per il numero di millisecondi specificato, un blocco esclusivo sull'oggetto specificato e imposta atomicamente un valore che indica se il blocco è stato ottenuto.

TryEnter(Object, TimeSpan)

Viene eseguito, per una quantità di tempo specificata, il tentativo di acquisire un blocco esclusivo sull'oggetto specificato.

TryEnter(Object, TimeSpan, Boolean)

Prova ad acquisire, per la quantità di tempo specificata, un blocco esclusivo sull'oggetto specificato e imposta atomicamente un valore che indica se il blocco è stato ottenuto.

Wait(Object)

Rilascia il blocco su un oggetto e interrompe il thread corrente finché riacquisisce il blocco.

Wait(Object, Int32)

Rilascia il blocco su un oggetto e interrompe il thread corrente finché riacquisisce il blocco. Allo scadere dell'intervallo di timeout specificato, il thread viene inserito nella coda di thread pronti.

Wait(Object, Int32, Boolean)

Rilascia il blocco su un oggetto e interrompe il thread corrente finché riacquisisce il blocco. Allo scadere dell'intervallo di timeout specificato, il thread viene inserito nella coda di thread pronti. Questo metodo consente anche di specificare se il dominio di sincronizzazione per il contesto, qualora si trovi in un contesto di sincronizzazione, viene terminato prima dell'attesa e riacquisito in un secondo momento.

Wait(Object, TimeSpan)

Rilascia il blocco su un oggetto e interrompe il thread corrente finché riacquisisce il blocco. Allo scadere dell'intervallo di timeout specificato, il thread viene inserito nella coda di thread pronti.

Wait(Object, TimeSpan, Boolean)

Rilascia il blocco su un oggetto e interrompe il thread corrente finché riacquisisce il blocco. Allo scadere dell'intervallo di timeout specificato, il thread viene inserito nella coda di thread pronti. Esce eventualmente dal dominio di sincronizzazione per il contesto di sincronizzazione prima dell'attesa e riacquisisce il dominio in un secondo momento.

Si applica a

Thread safety

Questo tipo è thread-safe.

Vedi anche