Dal .NET 4 è stato introdotto un nuovo tipo di ManualResetEvent chiamato ManualResetEventSlim
che permette di avere delle performance migliori qualora il tempo di blocco atteso sia molto breve.
Questo miglioramento viene effettuato effettuando dello spinning per un determinato numero di operazioni prima di effettuare il context switch e passare al blocking.
Permette, inoltre, di cancellare un Wait
utilizzando un CancellationToken
, cosa impossibile con i classici ManualResetEvent
.
La classe permette o a costruttore o mediante la property SpinCount
di impostare il numero di spin da effettuare prima di effettuare un vero block.
Utilizzo
L’utilizzo di un ManualResetEventSlim
è estremamente simile al suo simile ManualResetEvent
, le differenze sono le seguenti:
- Il metodo
WaitOne()
deiManualResetEvent
è stato rinominato inWait()
che ha in ingresso, oltre ad un timeout in ms, anche unTimeSpan
o unCancellation[[Token]]
. - Presenta una property get-only
IsSet
che permette di sapere se è stato o meno settato (di fatto analoga aWaitOne(0)
)
Chi preferire?
Dal libro C# 9.0 in a Nutshell leggo che ManualResetEventSlim
può essere fino a 50 volte più veloce in scenari dove l’attesa è minima in quanto non vi è alcun passaggio al sistema operativo.
Per capire quanto deve essere minima questa attesa ho creato il seguente benchmark:
Che porta ai seguenti risultati:
Come si può notare quando il tempo di attesa è maggiore o uguale a 1ms i due metodi si equivalgono. La differenza sostanziale si ha quando ho un tempo di sleep di 0ms (quindi un solo context switch senza ulteriori attese) dove ho un miglioramento di performance di 6x.
Per rimuovere l’overhead della creazione dei task ho pensato a questo ulteriore benchmark:
In questo esempio al momento del wait il ManualResetEvent
è già stato impostato a true, di fatto non ho quindi alcuna attesa.
I risultati sono i seguenti:
Il ManualResetEventSlim
, qualora il semaforo sia già stato settato, è decisamente più veloce; stiamo parlando di circa 2 ordini di grandezza.
Conclusioni
Nella stragrande maggioranza dei casi utilizzare un ManualResetEvent
è più che sufficiente, la differenza di performance è trascurabile.
Ha senso utilizzare il ManualResetEventSlim
in caso eventi dove spesso l’attesa è nulla (è già stato effettuato un Set()
) oppure inferiore a 1ms.