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 ...try
finally
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 SyncUpdateResource
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
. 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.