Sto sviluppando una applicazione abbastanza semplice usando le Windows Forms del Framework .NET 1.1, talmente semplice che in due giorni è bell'e pronta... quello che mi serve praticamente è una specie di procedura wizard-like, avviabile da una applicazione unmanaged, per esportare una serie di dati propri della applicazione stessa e generare un documento in formato XML che descriva i dati esportati.

Essenzialmente quello che l'applicazione deve fare è richiedere all'utente una serie di opzioni di configurazione, chiedergli la cartella dove salvare i dati da esportare, e quindi avviare una procedura di copia dei files e di generazione di un xml "descrittivo" dei dati copiati... niente di sconvolgente, anzi! :)

Ovviamente la fase "di lavoro", cioè la copia dei dati nella cartella di destinazione e la generazione del documento xml, deve andare su un thread separato, e l'interfaccia deve rimanere "attiva" per mostrare lo stato dei lavori (tramite ProgressBar) e attendere l'eventuale richiesta di cancellazione delle operazioni...

Tutto questo richiede una "decente" progettazione della gestione dei thread (cosa che tra l'altro credo Raffaele Rialdi abbia affrontato ai Community Days...), e l'uso di Invoke e salse varie per comunicare con la UI... ma se siete pigri come me e volete qualcosa di già pronto e utilizzabile per "lavori semplici" da eseguire in background, ma non volete/potete usare il FW 2.0 e il suo controllo BackGroundWorker, potete usare il componente BackGroundWorker 1.1 e 2.0 compliant sviluppato da Francesco Balena!

Il codice del componente è disponibile qui, e devo dire che è molto versatile, anche rispetto all'analogo componente fornito nel framework 2.0; ad esempio permette di impostare la priorità del thread secondario, di assegnare un nome al thread stesso, di terminare o sospendere il thread in esecuzione, di specificare se il thread di esecuzione deve essere un oggetto System.Threading.Thread oppure un thread dal ThreadPool...

L'uso del componente è davvero triviale: si trascina nella form, si impostano le proprietà e si implementani gli eventi DoWork (il metodo che verrà eseguito nel thread secondario), ProgressChanged (in cui implementare le modifiche della UI in risposta all'avanzamento dei lavori) e RunWorkerCompleted (quello che deve succedere quando il lavoro è terminato).

Il metodo DoWork è effettivamente quello basilare, va implementato in questo modo:

private void backgroundWorker_DoWork(object sender, CodeArchitects.ComponentModel.DoWorkEventArgs e)
{
    
// Recupero degli argomenti da utilizzare all'interno del metodo
    
object[] args = (object[])e.Argument;
    
    
// Refresh della UI        
    
backgroundWorker.ReportProgress(10, "Avvio lavori");
    Thread.Sleep(1000);

    
// Richiesta terminazione anticipata del thread    
    
if(backgroundWorker.CancellationPending) return    
    
backgroundWorker.ReportProgress(50, "Prosecuzione lavori");
    Thread.Sleep(1000);
    
    
if(backgroundWorker.CancellationPending) return    
    
backgroundWorker.ReportProgress(90, "Terminazione lavori");    
    Thread.Sleep(1000);
                
    e.Result = "Progetto PocketShArc esportato correttamente!";
}

All'interno del metodo DoWork occorre controllare periodicamente che l'utente non abbia richiesto di interrompere i lavori: il componente ci offre la proprietà (in sola letturabackgroundWorker.CancellationPending, adatta allo scopo.

Come già detto, il metodo ProgressChanged avvisa la UI che i lavori stanno procedendo:

private void backgroundWorker_ProgressChanged(object sender, CodeArchitects.ComponentModel.ProgressChangedEventArgs e)
{
    
// Il valore da assegnare alla progressBar
    
progressBar.Value = e.ProgressPercentage;
    
// Testo da assegnare ad una label...
    // o qualsiasi altra cosa visto che e.UserState è di tipo object!
    
progressLabel.Text = e.UserState.ToString();
}

Al termine dei lavori invece...

private void backgroundWorker_RunWorkerCompleted(object sender, CodeArchitects.ComponentModel.RunWorkerCompletedEventArgs e)
{        
    progressBar.Value = 100;
    
// e.Result è anch'essa di tipo object, ed è stata impostata in DoWork
    
progressLabel.Text = e.Result.ToString();            
}

Ok, tutto bello e facile, ma come cavolo si fa a farlo partire e a interromperlo?

/*
 * Avvio del componente passandogli alcuni argomenti
 * che recupererò poi nel metodo DoWork (in cui infatti facevo il cast a object[] ...)
 */
object args = new object[] {"ciao", "mondo", "io lavoro in background"}
backgroundWorker.RunWorkerAsync(args);

/*
 * Interruzione del componente: la cancellazione è ASINCRONA,
 * in pratica viene impostato a true il valore backgroundWorker.CancellationPending:
 * sarà il nostro codice, in DoWork, a dover gestire come comportarsi... 
 */
backgroundWorker.CancelAsync();

In realtà è possibile pure accedere direttamente al Thread secondario, e fare cose tipo BackgroundWorker1.Thread.Abort() ... ma a me sembra un pò una porcata...

Insomma, il componente merita più di uno sguardo, quindi vi invito a darci una occhiata

L'unica cosa... nel codice del componente e nella pagina di download dello stesso non si parla di licenze d'uso, o almeno io non ho visto nessun accenno a restrizioni di alcun tipo... spero questo significhi che il codice è rilasciato "as is" ed è utilizzabile senza limitazioni...

 

powered by IMHO 1.3