Area di riferimento
- Implementing service processes, threading, and application domains in a .NET Framework application
- Develop multithreaded .NET Framework applications
- AutoResetEvent class and ManualResetEvent class
- EventWaitHandle class, RegisterWaitHandle class, SendOrPostCallback delegate and IOCompletionCallback delegate
EventWaitHandle
L'oggetto EventWaitHandle ( e gli oggetti derivati AutoResetEvent e ManualResetEvent ) permette di gestire attraverso segnali la sincronizzazione tra diversi thread.
I metodi principali sono:
- Set() che imposta lo stato dell'evento a segnalato, permettendo a uno o più dei thread in attesa di procedere
- Reset() che imposta lo stato dell'evento a non segnalato, che causerà il blocco dei thread
- WaitOne() che blocca il thread corrente se l'evento non è segnalato altrimenti concede l'accesso.
La differenza tra un AutoResetEvent e un ManualResetEvent è semplicemente che dopo la WaitOne() nel primo caso viene effettuato il Reset() in automatico mentre nel secondo caso bisognerà gestire manualmente il reset dell'evento.
Per vedere in azione questa classe consideriamo un esempio. Supponiamo di avere un unico thread produttore e diversi thread consumatori. Il dato prodotto dal produttore è un numero intero. Si vuole fare in modo che una volta prodotto il dato il produttore si metta in attesa che venga consumato da qualche consumatore e che ciascun consumatore si metta in attesa che un dato venga prodotto ( non ho realizzato un meccanismo bufferizzato per semplicità ).
class Program
{
private const int N_COMSUMATORI = 5; // numero totale dei consumatori
private const int N_LETTURE = 3; // numero totale di letture per consumatore
private static EventWaitHandle datoPronto; // segnalato quando il dato è pronto
private static EventWaitHandle datoLetto; // segnalato quando il dato è stato letto
private static int cont = 1;
private static int dato = 0; // zona di memoria condivisa per la memorizzazione del dato
public static void Main(string[] args)
{
Thread[] threadConsumatori;
Thread threadProduttore;
// Costruisco gli oggetti EventWaitHandle impostati opportunamente
datoPronto = new EventWaitHandle(false, EventResetMode.AutoReset);
datoLetto = new EventWaitHandle(true, EventResetMode.AutoReset);
threadConsumatori = new Thread[N_COMSUMATORI];
// Faccio partire i thread consumatori
for (int i = 0; i < N_COMSUMATORI; i++)
{
threadConsumatori[i] = new Thread(new ParameterizedThreadStart(consumatore));
threadConsumatori[i].Start(i);
}
// Faccio partire il thread produttore
threadProduttore = new Thread(produttore);
threadProduttore.Start();
// Attendo che tutti i consumatori abbiano finito
for (int i = 0; i < N_COMSUMATORI; i++)
{
threadConsumatori[i].Join();
}
// termino il processo produttore
threadProduttore.Abort();
Console.Write("Fine");
Console.ReadKey();
}
public static void consumatore(object info)
{
int id = (int)info;
for(int i=0; i<N_LETTURE; i++)
{
// Attendo che un dato sia pronto
datoPronto.WaitOne();
// Utilizzo il dato
Console.WriteLine("Consumatore {0}: letto {1}", id, dato);
Thread.Sleep(1000);
// Segnalo che il dato è stato letto
datoLetto.Set();
}
Console.WriteLine("Consumatore {0} ha terminato", id);
}
public static void produttore(object info)
{
while ( true )
{
// attende che l'ultimo dato scritto venga consumato
datoLetto.WaitOne();
// produce un nuovo dato
dato = cont++;
Thread.Sleep(1000);
Console.WriteLine("Produttore: ho prodotto {0}", dato);
// segnala che un nuovo dato è pronto per essere consumato
datoPronto.Set();
}
Console.WriteLine("Il produttore ha terminato");
}
}
Ecco come si presenta l'output di una possibile esecuzione di questo codice:
Produttore: ho prodotto 1
Consumatore 3: letto 1
Produttore: ho prodotto 2
Consumatore 3: letto 2
Produttore: ho prodotto 3
Consumatore 3: letto 3
Consumatore 3 ha terminato
Produttore: ho prodotto 4
Consumatore 0: letto 4
Produttore: ho prodotto 5
Consumatore 0: letto 5
Produttore: ho prodotto 6
Consumatore 0: letto 6
Consumatore 0 ha terminato
Produttore: ho prodotto 7
Consumatore 1: letto 7
Produttore: ho prodotto 8
Consumatore 1: letto 8
Produttore: ho prodotto 9
Consumatore 4: letto 9
Produttore: ho prodotto 10
Consumatore 4: letto 10
Produttore: ho prodotto 11
Consumatore 1: letto 11
Consumatore 1 ha terminato
Produttore: ho prodotto 12
Consumatore 2: letto 12
Produttore: ho prodotto 13
Consumatore 2: letto 13
Produttore: ho prodotto 14
Consumatore 2: letto 14
Consumatore 2 ha terminato
Produttore: ho prodotto 15
Consumatore 4: letto 15
Consumatore 4 ha terminato
Fine
In particolare nell'esempio vengono utilizzati due oggetti EventWaitHandle uno datoPronto viene utilizzato dal produttore per segnalare quando un nuovo dato è disponibile mentre l'altro datoLetto viene utilizzo dal consumatore per segnalare al produttore che il dato appena prodotto è stato correttamente consumato.
E' importante notare che l'oggetto EventWaitHandle costruito utilizzando il valore EventResetMode.AutoReset corrisponde all'oggetto AutoResetEvent. L'oggetto EventWaitHandle costruito utilizzando il valore ManualResetMode.ManualReset corrisponde invece all'oggetto ManualResetEvent.
La classe RegisterWaitHandle e in particolare il suo metodo RegisterWaitForSingleObject() viene invece utilizzata per eseguire un metodo di callback quando uno specifico wait handle viene segnalato.
Per maggiori dettagli guardare l'esempio su msdn al seguente indirizzo http://msdn2.microsoft.com/en-us/library/system.threading.registeredwaithandle.aspx