Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
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.
Informazioni generali
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.
Annotazioni
Usare la Monitor classe per bloccare oggetti diversi dalle stringhe, ovvero tipi di riferimento diversi da String, non tipi valore. Per ulteriori dettagli, consultare gli overload del metodo Enter e la sezione Oggetto di blocco 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 metodi Enter e TryEnter. Un insieme di overload ha un parametro ref (in C#) o ByRef (in Visual Basic) Boolean che viene 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.
L'oggetto blocco
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 riferimento, non i tipi valore. Anche se è possibile passare un tipo di valore a Enter e Exit, viene incapsulato separatamente per ogni chiamata. Poiché ogni chiamata crea un oggetto separato, Enter non si blocca mai e il codice che sta presumibilmente proteggendo non è effettivamente sincronizzato. Inoltre, l'oggetto passato a Exit è diverso dall'oggetto passato a Enter, quindi Monitor genera SynchronizationLockException un'eccezione con il messaggio "Il metodo di sincronizzazione oggetti è stato chiamato da un blocco di codice non sincronizzato".
Nell'esempio seguente viene illustrato questo problema. Avvia dieci attività, ognuna delle quali dorme per 250 millisecondi. Ogni attività aggiorna quindi una variabile contatore, nTasks, che è destinata a contare il numero di attività effettivamente avviate ed eseguite. Poiché nTasks è una variabile globale che può essere aggiornata contemporaneamente da più attività, viene usato un monitoraggio per proteggerlo da modifiche simultanee da più attività. Tuttavia, come mostra l'output dell'esempio, ciascuna delle 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($"{nTasks} tasks started and executed.");
}
catch (AggregateException e)
{
String msg = String.Empty;
foreach (var ie in e.InnerExceptions)
{
Console.WriteLine($"{ie.GetType().Name}");
if (!msg.Contains(ie.Message))
msg += ie.Message + Environment.NewLine;
}
Console.WriteLine("\nException Message(s):");
Console.WriteLine(msg);
}
}
}
// The example displays the following output:
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
//
// Exception Message(s):
// Object synchronization method was called from an unsynchronized block of code.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example3
Public Sub Main()
Dim nTasks As Integer = 0
Dim tasks As New List(Of Task)()
Try
For ctr As Integer = 0 To 9
tasks.Add(Task.Run(Sub()
' Instead of doing some work, just sleep.
Thread.Sleep(250)
' Increment the number of tasks.
Monitor.Enter(nTasks)
Try
nTasks += 1
Finally
Monitor.Exit(nTasks)
End Try
End Sub))
Next
Task.WaitAll(tasks.ToArray())
Console.WriteLine("{0} tasks started and executed.", nTasks)
Catch e As AggregateException
Dim msg As String = String.Empty
For Each ie In e.InnerExceptions
Console.WriteLine("{0}", ie.GetType().Name)
If Not msg.Contains(ie.Message) Then
msg += ie.Message + Environment.NewLine
End If
Next
Console.WriteLine(vbCrLf + "Exception Message(s):")
Console.WriteLine(msg)
End Try
End Sub
End Module
' The example displays the following output:
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
'
' Exception Message(s):
' Object synchronization method was called from an unsynchronized block of code.
Ogni attività genera un'eccezione SynchronizationLockException perché la variabile nTasks viene messa in box prima della chiamata al metodo Monitor.Enter in ogni attività. In altre parole, ogni chiamata al metodo viene passata a una variabile separata indipendente dalle altre.
nTasks viene sottoposto nuovamente a boxing nella chiamata al metodo Monitor.Exit. Anche in questo caso, vengono create dieci nuove variabili incapsulate, indipendenti l'una dall'altra, nTasks e le dieci variabili incapsulate create nella chiamata al metodo Monitor.Enter. L'eccezione viene generata, quindi, perché il codice sta tentando di rilasciare un blocco su una variabile appena creata che non è stata bloccata in precedenza.
Sebbene sia possibile boxare una variabile di tipo valore prima di chiamare Enter e Exit, come illustrato nell'esempio seguente e passare lo stesso oggetto boxed a entrambi i metodi, non esiste alcun vantaggio per eseguire questa operazione. Le modifiche apportate alla variabile unboxed non vengono riflesse nella copia boxed e non è possibile modificare il valore della copia 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($"{nTasks} tasks started and executed.");
}
catch (AggregateException e) {
String msg = String.Empty;
foreach (var ie in e.InnerExceptions) {
Console.WriteLine($"{ie.GetType().Name}");
if (! msg.Contains(ie.Message))
msg += ie.Message + Environment.NewLine;
}
Console.WriteLine("\nException Message(s):");
Console.WriteLine(msg);
}
}
}
// The example displays the following output:
// 10 tasks started and executed.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example2
Public Sub Main()
Dim nTasks As Integer = 0
Dim o As Object = nTasks
Dim tasks As New List(Of Task)()
Try
For ctr As Integer = 0 To 9
tasks.Add(Task.Run(Sub()
' Instead of doing some work, just sleep.
Thread.Sleep(250)
' Increment the number of tasks.
Monitor.Enter(o)
Try
nTasks += 1
Finally
Monitor.Exit(o)
End Try
End Sub))
Next
Task.WaitAll(tasks.ToArray())
Console.WriteLine("{0} tasks started and executed.", nTasks)
Catch e As AggregateException
Dim msg As String = String.Empty
For Each ie In e.InnerExceptions
Console.WriteLine("{0}", ie.GetType().Name)
If Not msg.Contains(ie.Message) Then
msg += ie.Message + Environment.NewLine
End If
Next
Console.WriteLine(vbCrLf + "Exception Message(s):")
Console.WriteLine(msg)
End Try
End Sub
End Module
' The example displays the following output:
' 10 tasks started and executed.
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.
Annotazioni
La funzionalità fornita dai metodi Enter 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 racchiudono l'overload del metodo Monitor.Enter(Object, Boolean) e il metodo Monitor.Exit in un ...try
finally per assicurarsi che il monitor 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. In questo modo si garantisce 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, il meccanismo di blocco può essere ottenuto posizionando il System.Runtime.CompilerServices.MethodImplAttribute sul metodo e specificando il valore Synchronized 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 che le istruzioni Enter e Exit che bloccano e rilasciano un determinato oggetto possano attraversare i limiti di un membro, di una classe o di entrambi, non è consigliabile seguire questa pratica.
Pulse, PulseAll e Wait
Dopo che un thread possiede il blocco ed è entrato nella sezione critica che il blocco protegge, può chiamare i metodi Monitor.Wait, Monitor.Pulse e Monitor.PulseAll.
Quando il thread che detiene il lock chiama Wait, il lock viene rilasciato e il thread viene aggiunto alla coda d'attesa dell'oggetto sincronizzato. Il primo thread nella coda pronta, se presente, acquisisce il lock ed entra nella sezione critica. Il thread che ha chiamato Wait viene spostato dalla coda di attesa a quella pronta quando viene chiamato il metodo Monitor.Pulse o Monitor.PulseAll dal thread che ha il lock (per essere spostato, il thread deve trovarsi alla testa della coda di 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 Monitor classe e degli oggetti WaitHandle.
- La Monitor classe è puramente gestita, completamente portabile e potrebbe essere più efficiente in termini di requisiti delle risorse del sistema operativo.
- WaitHandle gli oggetti rappresentano oggetti waitable 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 possibilità di attendere molti 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 thread pool. 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: {(total * 1.0) / n:N2} (N={n:N0})");
}
catch (AggregateException e)
{
foreach (var ie in e.InnerExceptions)
Console.WriteLine($"{ie.GetType().Name}: {ie.Message}");
}
}
}
// The example displays output like the following:
// Mean for task 1: 499.04 (N=10,000)
// Mean for task 2: 500.42 (N=10,000)
// Mean for task 3: 499.65 (N=10,000)
// Mean for task 8: 502.59 (N=10,000)
// Mean for task 5: 502.75 (N=10,000)
// Mean for task 4: 494.88 (N=10,000)
// Mean for task 7: 499.22 (N=10,000)
// Mean for task 10: 496.45 (N=10,000)
// Mean for task 6: 499.75 (N=10,000)
// Mean for task 9: 502.79 (N=10,000)
//
// Mean for all tasks: 499.75 (N=100,000)
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example4
Public Sub Main()
Dim tasks As New List(Of Task)()
Dim rnd As New Random()
Dim total As Long = 0
Dim n As Integer = 0
For taskCtr As Integer = 0 To 9
tasks.Add(Task.Run(Sub()
Dim values(9999) As Integer
Dim taskTotal As Integer = 0
Dim taskN As Integer = 0
Dim ctr As Integer = 0
Monitor.Enter(rnd)
' Generate 10,000 random integers.
For ctr = 0 To 9999
values(ctr) = rnd.Next(0, 1001)
Next
Monitor.Exit(rnd)
taskN = ctr
For Each value In values
taskTotal += value
Next
Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
Task.CurrentId, taskTotal / taskN,
taskN)
Interlocked.Add(n, taskN)
Interlocked.Add(total, taskTotal)
End Sub))
Next
Try
Task.WaitAll(tasks.ToArray())
Console.WriteLine()
Console.WriteLine("Mean for all tasks: {0:N2} (N={1:N0})",
(total * 1.0) / n, n)
Catch e As AggregateException
For Each ie In e.InnerExceptions
Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message)
Next
End Try
End Sub
End Module
' The example displays output like the following:
' Mean for task 1: 499.04 (N=10,000)
' Mean for task 2: 500.42 (N=10,000)
' Mean for task 3: 499.65 (N=10,000)
' Mean for task 8: 502.59 (N=10,000)
' Mean for task 5: 502.75 (N=10,000)
' Mean for task 4: 494.88 (N=10,000)
' Mean for task 7: 499.22 (N=10,000)
' Mean for task 10: 496.45 (N=10,000)
' Mean for task 6: 499.75 (N=10,000)
' Mean for task 9: 502.79 (N=10,000)
'
' Mean for all tasks: 499.75 (N=100,000)
Poiché è possibile accedervi da qualsiasi attività in esecuzione su un thread del pool di thread, l'accesso alle variabili total e n deve essere anch'esso 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 internal classi Friend (in C#) o SyncResource (in Visual Basic) e UnSyncResource, che forniscono rispettivamente l'accesso sincronizzato e non sincronizzato a una risorsa. Per garantire che l'esempio illustra 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 Thread.ManagedThreadId proprietà è pari, il metodo chiama Thread.Sleep per introdurre un ritardo di 2.000 millisecondi. Si noti che, poiché la classe SyncResource non è pubblica, nessun codice client prende un blocco sulla risorsa sincronizzata; la classe interna stessa prende il blocco. In questo modo si impedisce a codice dannoso di eseguire un blocco su un oggetto pubblico.
using System;
using System.Threading;
internal class SyncResource
{
// Use a monitor to enforce synchronization.
public void Access()
{
lock(this) {
Console.WriteLine($"Starting synchronized resource access on thread #{Thread.CurrentThread.ManagedThreadId}");
if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
Thread.Sleep(2000);
Thread.Sleep(200);
Console.WriteLine($"Stopping synchronized resource access on thread #{Thread.CurrentThread.ManagedThreadId}");
}
}
}
internal class UnSyncResource
{
// Do not enforce synchronization.
public void Access()
{
Console.WriteLine($"Starting unsynchronized resource access on Thread #{Thread.CurrentThread.ManagedThreadId}");
if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
Thread.Sleep(2000);
Thread.Sleep(200);
Console.WriteLine($"Stopping unsynchronized resource access on thread #{Thread.CurrentThread.ManagedThreadId}");
}
}
public class App
{
private static int numOps;
private static AutoResetEvent opsAreDone = new AutoResetEvent(false);
private static SyncResource SyncRes = new SyncResource();
private static UnSyncResource UnSyncRes = new UnSyncResource();
public static void Main()
{
// Set the number of synchronized calls.
numOps = 5;
for (int ctr = 0; ctr <= 4; ctr++)
ThreadPool.QueueUserWorkItem(new WaitCallback(SyncUpdateResource));
// Wait until this WaitHandle is signaled.
opsAreDone.WaitOne();
Console.WriteLine("\t\nAll synchronized operations have completed.\n");
// Reset the count for unsynchronized calls.
numOps = 5;
for (int ctr = 0; ctr <= 4; ctr++)
ThreadPool.QueueUserWorkItem(new WaitCallback(UnSyncUpdateResource));
// Wait until this WaitHandle is signaled.
opsAreDone.WaitOne();
Console.WriteLine("\t\nAll unsynchronized thread operations have completed.\n");
}
static void SyncUpdateResource(Object state)
{
// Call the internal synchronized method.
SyncRes.Access();
// Ensure that only one thread can decrement the counter at a time.
if (Interlocked.Decrement(ref numOps) == 0)
// Announce to Main that in fact all thread calls are done.
opsAreDone.Set();
}
static void UnSyncUpdateResource(Object state)
{
// Call the unsynchronized method.
UnSyncRes.Access();
// Ensure that only one thread can decrement the counter at a time.
if (Interlocked.Decrement(ref numOps) == 0)
// Announce to Main that in fact all thread calls are done.
opsAreDone.Set();
}
}
// The example displays output like the following:
// Starting synchronized resource access on thread #6
// Stopping synchronized resource access on thread #6
// Starting synchronized resource access on thread #7
// Stopping synchronized resource access on thread #7
// Starting synchronized resource access on thread #3
// Stopping synchronized resource access on thread #3
// Starting synchronized resource access on thread #4
// Stopping synchronized resource access on thread #4
// Starting synchronized resource access on thread #5
// Stopping synchronized resource access on thread #5
//
// All synchronized operations have completed.
//
// Starting unsynchronized resource access on Thread #7
// Starting unsynchronized resource access on Thread #9
// Starting unsynchronized resource access on Thread #10
// Starting unsynchronized resource access on Thread #6
// Starting unsynchronized resource access on Thread #3
// Stopping unsynchronized resource access on thread #7
// Stopping unsynchronized resource access on thread #9
// Stopping unsynchronized resource access on thread #3
// Stopping unsynchronized resource access on thread #10
// Stopping unsynchronized resource access on thread #6
//
// All unsynchronized thread operations have completed.
Imports System.Threading
Friend Class SyncResource
' Use a monitor to enforce synchronization.
Public Sub Access()
SyncLock Me
Console.WriteLine("Starting synchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId)
If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
Thread.Sleep(2000)
End If
Thread.Sleep(200)
Console.WriteLine("Stopping synchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId)
End SyncLock
End Sub
End Class
Friend Class UnSyncResource
' Do not enforce synchronization.
Public Sub Access()
Console.WriteLine("Starting unsynchronized resource access on Thread #{0}",
Thread.CurrentThread.ManagedThreadId)
If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
Thread.Sleep(2000)
End If
Thread.Sleep(200)
Console.WriteLine("Stopping unsynchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId)
End Sub
End Class
Public Module App
Private numOps As Integer
Private opsAreDone As New AutoResetEvent(False)
Private SyncRes As New SyncResource()
Private UnSyncRes As New UnSyncResource()
Public Sub Main()
' Set the number of synchronized calls.
numOps = 5
For ctr As Integer = 0 To 4
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf SyncUpdateResource))
Next
' Wait until this WaitHandle is signaled.
opsAreDone.WaitOne()
Console.WriteLine(vbTab + Environment.NewLine + "All synchronized operations have completed.")
Console.WriteLine()
numOps = 5
' Reset the count for unsynchronized calls.
For ctr As Integer = 0 To 4
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf UnSyncUpdateResource))
Next
' Wait until this WaitHandle is signaled.
opsAreDone.WaitOne()
Console.WriteLine(vbTab + Environment.NewLine + "All unsynchronized thread operations have completed.")
End Sub
Sub SyncUpdateResource()
' Call the internal synchronized method.
SyncRes.Access()
' Ensure that only one thread can decrement the counter at a time.
If Interlocked.Decrement(numOps) = 0 Then
' Announce to Main that in fact all thread calls are done.
opsAreDone.Set()
End If
End Sub
Sub UnSyncUpdateResource()
' Call the unsynchronized method.
UnSyncRes.Access()
' Ensure that only one thread can decrement the counter at a time.
If Interlocked.Decrement(numOps) = 0 Then
' Announce to Main that in fact all thread calls are done.
opsAreDone.Set()
End If
End Sub
End Module
' The example displays output like the following:
' Starting synchronized resource access on thread #6
' Stopping synchronized resource access on thread #6
' Starting synchronized resource access on thread #7
' Stopping synchronized resource access on thread #7
' Starting synchronized resource access on thread #3
' Stopping synchronized resource access on thread #3
' Starting synchronized resource access on thread #4
' Stopping synchronized resource access on thread #4
' Starting synchronized resource access on thread #5
' Stopping synchronized resource access on thread #5
'
' All synchronized operations have completed.
'
' Starting unsynchronized resource access on Thread #7
' Starting unsynchronized resource access on Thread #9
' Starting unsynchronized resource access on Thread #10
' Starting unsynchronized resource access on Thread #6
' Starting unsynchronized resource access on Thread #3
' Stopping unsynchronized resource access on thread #7
' Stopping unsynchronized resource access on thread #9
' Stopping unsynchronized resource access on thread #3
' Stopping unsynchronized resource access on thread #10
' Stopping unsynchronized resource access on thread #6
'
' All unsynchronized thread operations have completed.
L'esempio definisce una variabile, numOps, che definisce il numero di thread che tenteranno di accedere alla risorsa. Il thread dell'applicazione chiama il ThreadPool.QueueUserWorkItem(WaitCallback) metodo per l'accesso sincronizzato e non sincronizzato cinque volte ciascuno. Il ThreadPool.QueueUserWorkItem(WaitCallback) metodo ha un singolo parametro, un delegato che non accetta parametri e non restituisce alcun valore. Per l'accesso sincronizzato, richiama il SyncUpdateResource metodo ; per l'accesso non sincronizzato richiama il UnSyncUpdateResource metodo . 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 a zero, indicando che tutti i thread sincronizzati hanno completato l'accesso alla risorsa, il metodo SyncUpdateResource chiama il metodo EventWaitHandle.Set, 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 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 UnSyncResource.Access metodo viene chiamato nell'ordine in cui i thread lo raggiungono.