Cenni preliminari sulle primitive di sincronizzazione

.NET offre una gamma di tipi che è possibile usare per sincronizzare l'accesso a una risorsa condivisa o coordinare l'interazione tra thread.

Importante

Usare la stessa istanza di primitiva di sincronizzazione per proteggere l'accesso a una risorsa condivisa. Se si usano istanze di primitive di sincronizzazione diverse per proteggere la stessa risorsa, sarà possibile aggirare la protezione fornita da una primitiva di sincronizzazione.

Classe WaitHandle e tipi di sincronizzazione leggeri

Più primitive di sincronizzazione .NET derivano dalla classe System.Threading.WaitHandle, che incapsula un handle di sincronizzazione del sistema operativo nativo e usa un meccanismo di segnalazione per l'interazione tra thread. Tali classi includono:

  • System.Threading.Mutex, che concede l'accesso esclusivo a una risorsa condivisa. Lo stato di un mutex viene segnalato se nessun thread lo possiede.
  • System.Threading.Semaphore, che limita il numero di thread che possono accedere simultaneamente a una risorsa condivisa o a un pool di risorse. Lo stato di un semaforo denominato è impostato come segnalato quando il relativo conteggio è maggiore di zero e come non segnalato quando il relativo conteggio è zero.
  • System.Threading.EventWaitHandle, che rappresenta un evento di sincronizzazione di thread e può trovarsi in uno stato segnalato o non segnalato.
  • System.Threading.AutoResetEvent, che deriva da EventWaitHandle e, quando segnalata si reimposta automaticamente in uno stato non segnalato dopo il rilascio di un singolo thread in attesa.
  • System.Threading.ManualResetEvent, che deriva da EventWaitHandle e, quando segnalata, rimane in uno stato segnalato finché non viene chiamato il metodo Reset.

In .NET Framework, poiché WaitHandle deriva da System.MarshalByRefObject, questi tipi possono essere usati per sincronizzare le attività dei thread tra limiti dei domini delle applicazioni.

In .NET Framework, .NET Core e .NET 5+, alcuni di questi tipi possono rappresentare handle di sincronizzazione di sistema denominati, che sono visibili in tutto il sistema operativo e possono essere usati per la sincronizzazione interprocesso:

Per altre informazioni, vedere le informazioni di riferimento sull'API WaitHandle.

I tipi di sincronizzazione leggeri non si basano su handle del sistema operativo sottostanti e in genere offrono prestazioni migliori. Tuttavia, non possono essere usati per la sincronizzazione interprocesso. Usare tali tipi per la sincronizzazione dei thread all'interno di un'applicazione.

Alcuni di questi tipi rappresentano alternative ai tipi derivati da WaitHandle. Ad esempio, SemaphoreSlim è un'alternativa leggera a Semaphore.

Sincronizzazione dell'accesso a una risorsa condivisa

.NET offre una gamma di primitive di sincronizzazione per controllare l'accesso a una risorsa condivisa da parte di più thread.

Monitor (classe)

La classe System.Threading.Monitor concede l'accesso con esclusione reciproca a una risorsa condivisa tramite l'acquisizione o il rilascio di un blocco sull'oggetto che identifica la risorsa. Mentre è attivo un blocco, il thread che contiene il blocco può ancora acquisire e rilasciare il blocco. Gli altri thread non possono acquisire il blocco e il metodo Monitor.Enter attende il rilascio del blocco. Il metodo Enter acquisisce un blocco rilasciato. È anche possibile usare il metodo Monitor.TryEnter per specificare l'intervallo di tempo durante il quale un thread cerca di acquisire un blocco. Poiché la classe Monitor presenta affinità di thread, il thread che ha acquisito un blocco deve rilasciare il blocco chiamando il metodo Monitor.Exit.

È possibile coordinare l'interazione tra i thread che acquisiscono un blocco sullo stesso oggetto usando i metodi Monitor.Wait, Monitor.Pulse e Monitor.PulseAll.

Per altre informazioni, vedere le informazioni di riferimento sull'API Monitor.

Nota

Usare l'istruzione lock in C# e l'istruzione SyncLock in Visual Basic per sincronizzare l'accesso a una risorsa condivisa invece di usare direttamente la classe Monitor. Queste istruzioni vengono implementate usando i metodi Enter e Exit e un blocco try…finally per garantire che il blocco acquisito venga sempre rilasciato.

Classe Mutex

La classe System.Threading.Mutex, analogamente a Monitor, concede l'accesso esclusivo a una risorsa condivisa. Usare uno degli overload del metodo Mutex.WaitOne per richiedere la proprietà di un mutex. Analogamente a Monitor, Mutex presenta affinità di thread e il thread che ha acquisito un mutex deve rilasciarlo chiamando il metodo Mutex.ReleaseMutex.

A differenza di Monitor, la classe Mutex può essere usata per la sincronizzazione interprocesso. A tale scopo, usare un mutex denominato, visibile in tutto il sistema operativo. Per creare un'istanza di mutex denominata, usare un costruttore di Mutex che specifica un nome. È anche possibile chiamare il metodo Mutex.OpenExisting per aprire un mutex di sistema denominato esistente.

Per altre informazioni, vedere l'articolo Mutex e le informazioni di riferimento sull'API Mutex.

Struttura SpinLock

La struttura System.Threading.SpinLock, analogamente a Monitor, concede l'accesso esclusivo a una risorsa condivisa in base alla disponibilità di un blocco. Quando SpinLock cerca di acquisire un blocco che non è disponibile, rimane in attesa in un ciclo eseguendo controlli ripetuti finché il blocco non diventa disponibile.

Per altre informazioni sui vantaggi e sugli svantaggi dell'uso del meccanismo di spinlock, vedere l'articolo SpinLock e le informazioni di riferimento sull'API SpinLock.

Classe ReaderWriterLockSlim

La classe System.Threading.ReaderWriterLockSlim concede l'accesso esclusivo a una risorsa condivisa per la scrittura e permette a più thread di accedere alla risorsa simultaneamente per la lettura. È possibile usare ReaderWriterLockSlim per sincronizzare l'accesso a una struttura dei dati condivisa che supporta operazioni di lettura thread-safe, ma richiede accesso esclusivo per eseguire le operazioni di scrittura. Quando un thread richiede l'accesso esclusivo, ad esempio chiamando il metodo ReaderWriterLockSlim.EnterWriteLock, le richieste di lettore e scrittore successive vengono bloccate finché tutti i lettori esistenti non escono dal blocco e lo scrittore non è entrato e uscito dal blocco.

Per altre informazioni, vedere le informazioni di riferimento sull'API ReaderWriterLockSlim.

Classi Semaphore e SemaphoreSlim

Le classi System.Threading.Semaphore e System.Threading.SemaphoreSlim limitano il numero di thread che possono accedere simultaneamente a una risorsa condivisa o a un pool di risorse. I thread aggiuntivi che richiedono la risorsa rimangono in attesa finché un thread non rilascia il semaforo. Poiché il semaforo non presenta affinità di thread, un thread può acquisire il semaforo e un altro può rilasciarlo.

La classe SemaphoreSlim è un'alternativa leggera a Semaphore e può essere usata solo per la sincronizzazione all'interno di un unico processo.

In Windows è possibile usare Semaphore per la sincronizzazione interprocesso. A tale scopo, creare un'istanza di Semaphore che rappresenta un semaforo di sistema denominato usando uno dei costruttori di Semaphore che specifica un nome o il metodo Semaphore.OpenExisting. SemaphoreSlim non supporta semafori di sistema denominati.

Per altre informazioni, vedere l'articolo Semaphore e SemaphoreSlim e le informazioni di riferimento sull'API Semaphore o SemaphoreSlim.

Interazione tra thread o segnalazione

L'interazione tra thread (o segnalazione tra thread) significa che un thread deve attendere la notifica, o un segnale, da uno o più thread per procedere. Se, ad esempio, il thread A chiama il metodo Thread.Join del thread B, il thread A è bloccato fino al completamento del thread B. Le primitive di sincronizzazione descritte nella sezione precedente forniscono un meccanismo diverso per la segnalazione. Rilasciando un blocco, un thread notifica a un altro thread che quest'ultimo può procedere acquisendo il blocco.

Questa sezione descrive i costrutti di segnalazione aggiuntivi forniti da .NET.

Classi EventWaitHandle, AutoResetEvent, ManualResetEvent e ManualResetEventSlim

La classe System.Threading.EventWaitHandle rappresenta un evento di sincronizzazione di thread.

Un evento di sincronizzazione può trovarsi in uno stato segnalato o non segnalato. Quando lo stato di un evento è non segnalato, un thread che chiama l'overload WaitOne dell'evento rimane bloccato fino a quando un evento non viene segnalato. Il metodo EventWaitHandle.Set imposta lo stato di un evento come segnalato.

Il comportamento di un oggetto EventWaitHandle che è stato segnalato dipende dalla modalità di reimpostazione:

In Windows è possibile usare EventWaitHandle per la sincronizzazione interprocesso. A tale scopo, creare un'istanza di EventWaitHandle che rappresenta un evento di sincronizzazione di sistema denominato usando uno dei costruttori di EventWaitHandle che specifica un nome o il metodo EventWaitHandle.OpenExisting.

Per altre informazioni, vedere l'articolo EventWaitHandle. Per informazioni di riferimento sulle API, vedere EventWaitHandle, AutoResetEvent, ManualResetEvent e ManualResetEventSlim.

Classe CountdownEvent

La classe System.Threading.CountdownEvent rappresenta un evento che viene impostato quando il relativo conteggio è zero. Quando CountdownEvent.CurrentCount è maggiore di zero, un thread che chiama CountdownEvent.Wait viene bloccato. Chiamare CountdownEvent.Signal per ridurre il conteggio di un evento.

A differenza di ManualResetEvent o ManualResetEventSlim, che è possibile usare per sbloccare più thread con un segnale da un thread, è possibile usare CountdownEvent per sbloccare uno o più thread con segnali da più thread.

Per altre informazioni, vedere l'articolo CountdownEvent e le informazioni di riferimento sull'API CountdownEvent.

Classe Barrier

La classe System.Threading.Barrier rappresenta una barriera di esecuzione di thread. Un thread che chiama il metodo Barrier.SignalAndWait segnala di aver raggiunto la barriera e attende fino a quando gli altri thread partecipanti non raggiungono la barriera. Quando tutti i thread partecipanti raggiungono la barriera, procedono e la barriera viene reimpostata e può essere usata di nuovo.

È possibile usare Barrier quando uno o più thread richiedono i risultati di altri thread prima di continuare con la fase di calcolo successiva.

Per altre informazioni, vedere l'articolo Barrier e le informazioni di riferimento sull'API Barrier.

Interlocked (classe)

La classe System.Threading.Interlocked fornisce metodi statici che eseguono operazioni atomiche semplici su una variabile. Queste operazioni atomiche includono addizione, incremento e decremento, scambio e scambio condizionale in base a un confronto, oltre che l'operazione di lettura di un valore intero a 64 bit.

Per altre informazioni, vedere le informazioni di riferimento sull'API Interlocked.

Struttura SpinWait

La struttura System.Threading.SpinWait fornisce supporto per l'attesa basata su rotazione. È possibile usare questa struttura quando un thread deve attendere che un evento venga segnalato o una condizione soddisfatta, ma quando si prevede che il tempo di attesa effettivo sia inferiore a quello richiesto usando un handle di attesa o bloccando in altro modo il thread corrente. Usando SpinWait, è possibile specificare un breve intervallo di tempo di rotazione durante l'attesa e quindi produrre un risultato (ad esempio, mediante l'attesa o la sospensione) solo se la condizione non viene soddisfatta nel tempo specificato.

Per altre informazioni, vedere l'articolo SpinWait e le informazioni di riferimento sull'API SpinWait.

Vedi anche