lunedì 26 maggio 2008

Il BackgroundWorker di .NET

E' un bel po' che non scrivo qualcosa di veramente nerd, per cui devo vedere di darmi un tono, che qua se no tra emo, tette e culi e gommisti che fanno l'happy hour sembra quasi un brog di quelli lascivi.

Lo spunto me l'ha offerto un mio amico a cui serviva un giochino .NET che eseguisse un'operazione lunga e ripetitiva (un send broadcast su un socket UDP...don't ask) pescando dati dalla gui e aggiornando la gui stessa; problema classico dei client grafici stand-alone è che quando esegui qualcosa di pesante nello stesso thread che dovrebbe anche aggiornare la gui, questa si freeza dando all'utente la sensazione di un crash.

Dato che è un problema stranoto, il .NET framework offre la soluzione, elegantemente spiegata in questo video:



Fondamentalmente, la classe BackgroundWorker fornisce un'interfaccia relativamente semplice per le operazioni di "Crea un thread - fagli fare quello che vuoi in background - a intervalli regolari fagli notificare il suo stato - alla fine fagli notificare che ha finito" mettendo a disposizione 3 delegate: con le 3 istruzioni

bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);

Si settano i delegate (o, per gli anziani che parlano in C, i callback) rispettivamente per le operazioni di "fai quello che devi fare", "notificami il tuo stato" e "hai finito il tuo compito".

Il delegate più importante, quello che fa tutto il lavoro sporco da fare in background, è il primo, la cui signature è
void bw_DoWork(object sender, DoWorkEventArgs e)
I due parametri rappresentano, rispettivamente, il BackgroundWorker che sta lavorando (upcastato a object per convenzione) e i parametri passati dall'applicazione al thread del lavoro sporco; a intervalli - si spera - regolari, in questo metodo comparirà una chiamata a sender.ReportProgress(int, object) con la quale si riporteranno al thread principale la propria percentuale di esecuzione e il proprio stato o altri dati interessanti, per esempio da riportare sulla gui.

La presentazione in gui dello stato del BackgroundWorker, infatti, viene implementata nel delegate associato all'evento ProgressChanged, scatenato ogni volta che DoWork chiama ReportProgress con una percentuale di esecuzione diversa da quella della chiamata precedente: in questo modo, è possibile separare completamente il layer di logica di business da quello di presentazione, con somma gioia di grandi e piccini :)

Una volta finito tutto il lavoro sporco in background, il BackgroundWorker spara l'evento RunWorkerCompleted, che fa eseguire i delegate associati e manda tutti negli spogliatoi per una doccia.

Come se non bastasse, è possibile fermare il BackgroundWorker a metà del suo operato senza alcuna ripercussione sulla fluidità della gui: è sufficiente, da qualche parte nel codice (per esempio nel metodo associato al click di un pulsante), chiamare il metodo CancelAsync del BackgroundWorker e, nel delegate associato a DoWork, controllare se è stata richiesta la cancellazione dell'esecuzione testando la proprietà CancellationPending.

Morale, con questo giochino si riesce a ottenere una gui fluida e sempre rapida nelle risposte all'utente anche quando, sotto sotto, il programma sta facendo qualcosa di pesante e noioso.

E io sono sempre un nerd, anche se qualche volta parlo di argomenti frivoli.

2 commenti:

Anonimo ha detto...

...
...
...

Preferivo i culi e le tette. -.-''

Raibaz ha detto...

In effetti anch'io...cercherò di non scrivere più cose così pesanti...sorry :(