Condividi tramite


Monitor

Aggiornamento: Luglio 2008

Gli oggetti Monitor espongono la possibilità di sincronizzare l'accesso a un'area di codice accettando e rilasciando un blocco su un determinato oggetto tramite i metodi Monitor.Enter, Monitor.TryEnter e Monitor.Exit. Una volta acquisito un blocco su un'area di codice, è possibile utilizzare i metodi Monitor.Wait, Monitor.Pulse e Monitor.PulseAll. Wait rilascia l'eventuale blocco e attende la notifica. Se Wait viene notificato, restituisce e ottiene di nuovo il blocco. Sia Pulse che PulseAll segnalano al thread successivo nella coda di attesa di procedere.

Le istruzioni SyncLock di Visual Basic e lock di C# utilizzano Monitor.Enter per acquisire il blocco e Monitor.Exit per rilasciarlo. Il vantaggio dell'utilizzo delle istruzioni di linguaggio è che tutto il contenuto di un blocco lock o SyncLock è incluso in un'istruzione Try. L'istruzione Try include un blocco Finally per garantire il rilascio del blocco.

Monitor consente di bloccare gli oggetti, ovvero i tipi di riferimento, ma non i tipi di valore. Quando un tipo di valore viene passato a Enter ed Exit, ne viene eseguito il boxing per ogni singola chiamata. Poiché con ogni chiamata viene creato un oggetto separato, Enter non si blocca mai e il codice apparentemente protetto non è in realtà sincronizzato. Inoltre, poiché l'oggetto passato a Exit è diverso dall'oggetto passato a Enter, Monitor genera SynchronizationLockException con il messaggio "Metodo di sincronizzazione dell'oggetto chiamato da un blocco di codice non sincronizzato". Nell'esempio che segue vengono illustrati questi problemi.

Private x As Integer
' The next line creates a generic object containing the value of 
' x each time the code is executed, so that Enter never blocks.
Monitor.Enter(x)
Try
    ' Code that needs to be protected by the monitor.
Finally
    ' Always use Finally to ensure that you exit the Monitor.
    ' The following line creates another object containing 
    ' the value of x, and throws SynchronizationLockException
    ' because the two objects do not match.
    Monitor.Exit(x)
End Try
private int x;
// The next line creates a generic object containing the value of
// x each time the code is executed, so that Enter never blocks.
Monitor.Enter(x);
try {
    // Code that needs to be protected by the monitor.
}
finally {
    // Always use Finally to ensure that you exit the Monitor.
    // The following line creates another object containing 
    // the value of x, and throws SynchronizationLockException
    // because the two objects do not match.
    Monitor.Exit(x);
}

Sebbene sia possibile eseguire il boxing di una variabile di tipo di valore prima di chiamare Enter ed Exit, come nell'esempio che segue, e passare lo stesso oggetto boxed a entrambi i metodi, questa operazione non comporta alcun vantaggio. Le modifiche apportate alla variabile non vengono riflesse nella copia boxed di cui non è possibile modificare il valore.

Private o As Object = x
private Object o = x;

È importante notare la distinzione tra l'utilizzo degli oggetti Monitor e WaitHandle. Gli oggetti Monitor sono semplicemente gestiti, completamente portabili e potrebbero essere più efficienti in termini di requisiti di risorse del sistema operativo. Gli oggetti WaitHandle sono oggetti del sistema operativo che rendono possibile l'attesa, sono utili per la sincronizzazione tra codice gestito e non gestito ed espongono alcune funzionalità avanzate del sistema operativo, quali la capacità di attendere su molti oggetti contemporaneamente.

Nell'esempio di codice seguente viene illustrato l'utilizzo combinato della classe Monitor, implementata con le istruzioni del compilatore lock e SyncLock, della classe Interlocked e della classe AutoResetEvent.

Imports System
Imports System.Threading
Imports Microsoft.VisualBasic

' Note: The class whose internal public member is the synchronizing method
' is not public; none of the client code takes a lock on the Resource object.
' The member of the nonpublic class takes the lock on itself. Written this 
' way, malicious code cannot take a lock on a public object.
Class SyncResource
   
   Public Sub Access(threadNum As Int32)
      ' Uses Monitor class to enforce synchronization.
      SyncLock Me
         ' Synchronized: Despite the next conditional, each thread 
         ' waits on its predecessor.
         If threadNum Mod 2 = 0 Then
            Thread.Sleep(2000)
         End If
         Console.WriteLine("Start Synched Resource access (Thread={0})", threadNum)
         Thread.Sleep(200)
         Console.WriteLine("Stop Synched Resource access (Thread={0})", threadNum)
      End SyncLock
   End Sub 'Access
End Class 'SyncResource

' Without the lock, the method is called in the order in which 
' threads reach it.
Class UnSyncResource
   
   Public Sub Access(threadNum As Int32)
      ' Does not use Monitor class to enforce synchronization.
      ' The next call throws the thread order.
      If threadNum Mod 2 = 0 Then
         Thread.Sleep(2000)
      End If
      Console.WriteLine("Start UnSynched Resource access (Thread={0})", threadNum)
      Thread.Sleep(200)
      Console.WriteLine("Stop UnSynched Resource access (Thread={0})", threadNum)
   End Sub 'Access
End Class 'UnSyncResource

Public Class App
   Private Shared numAsyncOps As Int32 = 5
   Private Shared asyncOpsAreDone As New AutoResetEvent(False)
   Private Shared SyncRes As New SyncResource()
   Private Shared UnSyncRes As New UnSyncResource()
   Private Shared threadNum As Int32
   Public Shared Sub Main()
      
      For threadNum = 0 To 4
         ThreadPool.QueueUserWorkItem(AddressOf SyncUpdateResource, threadNum)
      Next threadNum
      
      ' Wait until this WaitHandle is signaled.
      asyncOpsAreDone.WaitOne()
      Console.WriteLine(ControlChars.Tab + ControlChars.Lf + "All synchronized operations have completed." + ControlChars.Lf)
      
      ' Reset the thread count for unsynchronized calls.
      numAsyncOps = 5
      
      For threadNum = 0 To 4
         ThreadPool.QueueUserWorkItem(AddressOf UnSyncUpdateResource, threadNum)
      Next threadNum
      
      ' Wait until this WaitHandle is signaled.
      asyncOpsAreDone.WaitOne()
      Console.WriteLine(ControlChars.Tab + ControlChars.Cr + "All unsynchronized thread operations have completed.")
   End Sub 'Main
   
   
   
   ' The callback method's signature MUST match that of 
   ' a System.Threading.TimerCallback delegate
   ' (it takes an Object parameter and returns void).
   Shared Sub SyncUpdateResource(state As Object)
      ' This calls the internal synchronized method, passing 
      ' a thread number.
      SyncRes.Access(CType(state, Int32))
      
      ' Count down the number of methods that the threads have called.
      ' This must be synchronized, however; you cannot know which thread 
      ' will access the value **before** another thread's incremented 
      ' value has been stored into the variable.
      If Interlocked.Decrement(numAsyncOps) = 0 Then
         asyncOpsAreDone.Set() 
         ' Announce to Main that in fact all thread calls are done.
      End If
   End Sub 'SyncUpdateResource
    
   ' The callback method's signature MUST match that of 
   ' a System.Threading.TimerCallback delegate
   ' (it takes an Object parameter and returns void).
   Shared Sub UnSyncUpdateResource(state As [Object])
      ' This calls the unsynchronized method, passing 
      ' a thread number.
      UnSyncRes.Access(CType(state, Int32))
      
      ' Count down the number of methods that the threads have called.
      ' This must be synchronized, however; you cannot know which thread 
      ' will access the value **before** another thread's incremented 
      ' value has been stored into the variable.
      If Interlocked.Decrement(numAsyncOps) = 0 Then
         asyncOpsAreDone.Set() 
         ' Announce to Main that in fact all thread calls are done.
      End If
   End Sub 'UnSyncUpdateResource 
End Class 'App
using System;
using System.Threading;

// Note: The class whose internal public member is the synchronizing 
// method is not public; none of the client code takes a lock on the 
// Resource object.The member of the nonpublic class takes the lock on 
// itself. Written this way, malicious code cannot take a lock on 
// a public object.
class SyncResource {
   public void Access(Int32 threadNum) {
      // Uses Monitor class to enforce synchronization.
      lock (this) {
       // Synchronized: Despite the next conditional, each thread 
       // waits on its predecessor.
       if (threadNum % 2 == 0)
         Thread.Sleep(2000);
         Console.WriteLine("Start Synched Resource access (Thread={0})", threadNum);
         Thread.Sleep(200);
         Console.WriteLine("Stop Synched Resource access (Thread={0})", threadNum);
      }
   }
}

// Without the lock, the method is called in the order in which threads reach it.
class UnSyncResource {
   public void Access(Int32 threadNum) {
    // Does not use Monitor class to enforce synchronization.
    // The next call throws the thread order.
    if (threadNum % 2 == 0)
      Thread.Sleep(2000);
     Console.WriteLine("Start UnSynched Resource access (Thread={0})", threadNum);
     Thread.Sleep(200);
     Console.WriteLine("Stop UnSynched Resource access (Thread={0})", threadNum);
   }
}

public class App {
   static Int32 numAsyncOps = 5;
   static AutoResetEvent asyncOpsAreDone = new AutoResetEvent(false);
   static SyncResource SyncRes = new SyncResource();
   static UnSyncResource UnSyncRes = new UnSyncResource();

   public static void Main() {

      for (Int32 threadNum = 0; threadNum < 5; threadNum++) {
         ThreadPool.QueueUserWorkItem(new WaitCallback(SyncUpdateResource), threadNum);
      }

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

     // Reset the thread count for unsynchronized calls.
     numAsyncOps = 5;

      for (Int32 threadNum = 0; threadNum < 5; threadNum++) {
         ThreadPool.QueueUserWorkItem(new WaitCallback(UnSyncUpdateResource), threadNum);
      }

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


   // The callback method's signature MUST match that of a 
   // System.Threading.TimerCallback delegate (it takes an Object 
   // parameter and returns void).
   static void SyncUpdateResource(Object state) {
     // This calls the internal synchronized method, passing 
     // a thread number.
      SyncRes.Access((Int32) state);

     // Count down the number of methods that the threads have called.
     // This must be synchronized, however; you cannot know which thread 
     // will access the value **before** another thread's incremented 
     // value has been stored into the variable.
      if (Interlocked.Decrement(ref numAsyncOps) == 0)
         asyncOpsAreDone.Set(); 
         // Announce to Main that in fact all thread calls are done.
   }

   // The callback method's signature MUST match that of a 
   // System.Threading.TimerCallback delegate (it takes an Object 
   // parameter and returns void).
   static void UnSyncUpdateResource(Object state) {
     // This calls the unsynchronized method, passing a thread number.
      UnSyncRes.Access((Int32) state);

     // Count down the number of methods that the threads have called.
     // This must be synchronized, however; you cannot know which thread 
     // will access the value **before** another thread's incremented 
     // value has been stored into the variable.
      if (Interlocked.Decrement(ref numAsyncOps) == 0)
         asyncOpsAreDone.Set(); 
         // Announce to Main that in fact all thread calls are done.
   }
}

Vedere anche

Riferimenti

Monitor

Altre risorse

Oggetti e funzionalità del threading

Cronologia delle modifiche

Data

Cronologia

Motivo

Luglio 2008

Aggiunto chiarimento: le istruzioni SyncLock e lock utilizzano Monitor.Enter e Exit.

Commenti e suggerimenti dei clienti.