Ve l'avevo promesso che avrei ricominciato. Dopo questa
lunga pausa, dovuta un po' a motivi di lavoro e salute, vediamo oggi di
riprendere la mia serie di post per l'esame 70-536.
Questa ripresa dei lavori vede come protagonista la class Thread ed affini, ovvero tutte
quelle classi definite nel namespace System.Threading che ci permettono di eseguire
operazioni in modo asincrono. Ne avevamo già parlato in passato quando avevamo
trattato il nuovo componente BackgroundWorker: con nuovo,
intendo con il FX2.0. Il BackgroundWorker internamente fa ovviamente uso di
thread secondari, e risolve la maggior parte delle problematiche riguardanti la
gestione della UI. Come sappiamo, infatti, non possiamo manipolare i controlli
sulla Windows Forms al di fuori del thread che le ha create: se abbiamo un task
che dura un po' di tempo, e vogliamo ogni tanto notificare qualcosa su una
Label, per esempio, non possiamo farlo in modo diretto come faremmo di solito,
ma dobbiamo avere qualche accorgimento in più. Il
BackgroundWorker semplifica e gestisce direttamente questi
accorgimenti.
Se abbiamo bisogno di qualcosa di più performante, oppure non abbiamo UI da
gestire (magari stiamo sviluppando un web-services), possiamo lavorare con la
classe Thread. Vediamo come.
Introduzione alla classe Thread
Non mi capita tutti i
giorni di sviluppare software che abbiano bisogno di thread secondari. Per
questo motivo, ho sviluppato una piccola applicazione di test. In pratica, una
semplice Windows Forms che si connette ad un database SQL Server, chiede quanti
record si vogliono inserire e poi comincia a lanciare INSERT INTO su una
tabella. La tabella non è importante (ha un campo identity ed un varchar(50)):
ci basta sapere che per aggiungere 20.000 records, ci metto circa 20
secondi.
All'interno della form, ho creato un metodo privato aggiungiRecord:
private void aggiungiRecord()
{
// codice
for (int cycle = 0; cycle <= 20000; cycle++)
{
msg = "Marca " + cycle.ToString();
cmd.Parameters[0].Value = msg;
cmd.ExecuteNonQuery();
updateUI.BeginInvoke(msg, null, null);
}
// codice
}
Ho eliminato le parti di codice non interessanti. Sul click di un pulsante,
vogliamo eseguire il metodo qui sopra in un thread separato, cosicchè questo
comincia a fare 20.000 inserimenti senza interrompere il nostro lavoro. Oltre
alla classe Thread vera e propria, abbiamo bisogno di una delle due classi:
ThreadStart oppure ParameterizedThreadStart. La
prima se il metodo che vogliamo eseguire non ha parametri di input (come nel
nostro caso), la seconda se invece c'è qualche parametro.
Quindi, per
esempio:
ThreadStart start = new ThreadStart(aggiungiRecord);
_th = new Thread(start);
_th.Start();
Questo è la tecnica assolutamente più semplice: al click del pulsante, il
thread parte ed esegue il codice. Il thread non ha nome, non ha priorità. Se il
metodo aggiungiRecord avesse un parametro, dobbiamo usare un altro modalità:
int howMany;
int.TryParse(txtHowManyRecords.Text, out howMany);
ParameterizedThreadStart start = new ParameterizedThreadStart(aggiungiRecord);
_th = new Thread(start);
_th.Start(howMany);
Ho aggiunto sulla form una TextBox chiamata txtHowManyRecords che
l'utente può usare per esprimere quanti record vuole inserire in tabella.
Utilizzo il metodo statico TryParse della classe int: il metodo
ritorna un bool, e in howMany finisce il numero richiesto. Il thread viene
inizializzato con la classe ParameterizedThreadStart: per farlo, ho dovuto
ovviamente modificare la firma di aggiungiRecord in questo modo:
private void aggiungiRecord(object o)
{ }
Il thread sta girando...e allora?
Giunti a questo punto,
il thread sta girando. Il thread secondario sta eseguendo il codice del metodo
aggiungiRecord. Probabilmente, il ciclo for inserito occuperà gran parte del
tempo. All'interno di questo ciclo, possiamo notificare sulla UI qualsiasi cosa
all'utente, ma con le dovute attenzioni che - credo - esulano un po' dallo scopo
di questo post. Basta giocherellare con delegate, con BeginInvoke, InvokeRequired e dintorni, ed il gioco è fatto. Personalmente,
avevo studiato il capitolo 14 "Multithreaded User Interfaces" di "Windows Forms
Programming in C#", scritto da Chris Sells: credo il miglior capitolo di quel
libro.
Maggior controllo sul thread
La classe Thread ha alcune
caratteristiche molto interessanti, che consentono al nostro codice di
controllarla, magari via UI. Innanzitutto, il thread secondario può girare con
una cultura differente da quella standard (proprietà CurrentCulture e CurrentUICulture). Possiamo dargli un nome (proprietà Name) e capire in quale stato si trova (ThreadState, che è un'enum). Possiamo usare il metodo Suspend per interrompere l'esecuzione e il metodo Resume per riprenderla. Il metodo Abort blocca del tutto l'esecuzione del thread.
Vedremo nel prossimo post qualche dettaglio in più, e il download
dell'applicazione di esempio che ho creato.