Creare variabili locali al thread con il .NET Framework 4.0
Il multithreading e la parallelizzazione sono tematiche che sono state prese molto in considerazione con il .NET Framework 4.0. Oltre al Parallel Framework, vi sono molti ritocchi e nuove funzionalità che già si è avuto modo di vedere con altri script. Una delle esigenze più comuni di cui si necessita quando si lavora con più thread è di mantenere dei valori di una stessa variabile a livello di thread, in modo che ogni thread lavori con oggetti diversi e possa manipolare la variabile come vuole.
Per raggiungere tale risultato, fin dalle prime versioni è possibile utilizzare l'attributo ThreadStatic sul membro della classe. Nel seguente esempio si suppone di eseguire un'azione parallelamente su più per thread, per far scrivere a video l'ID del thread che lo sta eseguendo, assicurandosi che tale valore venga inizializzato una sola volta per thread.
[ThreadStatic()] private static int threadId; static void Main(string[] args) { Action<int> a = i => { if (threadId == 0) threadId = Thread.CurrentThread.ManagedThreadId; Console.WriteLine("Iteration {0} on thread {1}", i, threadId); }; Parallel.For(1, 10, new ParallelOptions(), a); }
Eseguendolo, poiché i core sono due, alcuni thread verranno usati più di una volta e a video verrà stampato il seguente testo.
Iteration 1 on thread 1 Iteration 2 on thread 1 Iteration 3 on thread 1 Iteration 4 on thread 1 Iteration 6 on thread 1 Iteration 7 on thread 1 Iteration 8 on thread 1 Iteration 5 on thread 4 Iteration 9 on thread 3
Lo snippet precedente presenta però alcuni difetti. Prima di tutto si è obbligati a ricorrere ad una classe e ad un campo per poter memorizzare il valore per thread. Inoltre, ad ogni iterazione si è obbligati a controllare che la variabile sia inizializzata e provvedere a farlo qualora sia null o corrisponda ad una costante (in questo caso zero dell'intero). Non vi è altro modo poiché usando l'attributo ThreadStatic non è possibile utilizzare l'inizializzazione inline della variabile:
private static int threadId = Thread.CurrentThread.ManagedThreadId;
Sebbene non comporti alcun errore, il costruttore statico della classe viene eseguito solo una volta e la variabile threadId verrà inizializzata una sola volta.
Per superare questi problemi viene in aiuto la nuova classe ThreadLocal<T>. Il suo utilizzo è molto simile a Lazy<T>, perché permette tramite la proprietà Value di ottenere il valore specifico per ogni thread, e di inizializzarlo attraverso una funzione. Ecco quindi come diventa l'esempio precedente che produrrà il medesimo risultato:
// Variabile locale per thread using (ThreadLocal<Int32> threadIdLocal = new ThreadLocal<int>(() => Thread.CurrentThread.ManagedThreadId)) { //Interrogo direttamente la proprietà Value Action<int> a = i => Console.WriteLine("Iteration {0} on thread {1}", i, threadIdLocal.Value); Parallel.For(1, 10, new ParallelOptions(), a); }
Il codice diventa più compatto, più facile da usare ed inoltre è molto performante. La classe ThreadLocal<T> lavora internamente con ThreadStatic e con un sistema a stack sincronizzato che gli permette di controllare e inizializzare facilmente la variabile per ogni thread. E' importante infine chiamare il Dispose dell'oggetto in modo da liberare lo stack per altri utilizzi.







