Dopo aver analizzato nel dettaglio la struttura e il funzionamento del sistema di threading, veniamo ora a vedere alcuni controlli che implementano questo meccanismo "dietro le quinte". Il primo di questi è BackgroundWorker, un controllo senza interfaccia grafica, che nelle Windows Application rende molto più facile l'utilizzo di thread separati per compiti diversi: infatti fornisce metodi e proprietà che fanno da wrapper a un thread separato. Ecco una lista dei membri più usati:
- CancelAsync : annulla le operazioni che il controllo sta svolgendo. Al pari di Thread.Abort, l'azione non è immediata e possono anche essere eseguite istruzioni che evitino l'aborto del thread
- CancellationPending : determina se è stato richiesto l'annullamento delle operazioni con CancelAsync
- IsBusy : determina se il controllo è in fase di esecuzione
- ReportProgress(I) : se richiamato della procedura principale del thread separato, genera un evento ProgressChanged contenente informazioni sullo stato dell'operazione. I è la percentuale di completamento del lavoro
- RunWorkerAsync : dà inizio alle operazioni tramite il controllo BackgroundWorker. Il suo overload accetta un solo parametro di tipo Object contenente i parametri che opzionalmente si devono passare alla procedura principale del thread separato
- WorkerReportProgress : determina se il controllo possa generare eventi ProgressChanged
- WorkerSupportCancellation : determina se il controllo supporta la cancellazione delle operazioni
Ora, il BackgroundWorker lavora come descritto di seguito. La procedura principale che deve essere eseguita nel thread separato va posta
in un evento speciale del controllo, chiamato DoWork: attraverso il parametro "e" è possibile anche ottenere altri dati necessari
alle operazioni da svolgere. Una volta che tutto il codice in DoWork è stato completato, viene lanciato l'evento RunWorkerCompleted.
Tale evento viene comunque generato anche nel caso in cui il sorgente abbia dato esito negativo (ad esempio a causa del verificarsi di
eccezioni gestite e non), oppure si sia richiamata la procedura CancelAsync. Sempre all'interno di DoWork si può usare il metodo ReportProgress
per comunicare all'applicazione principale un avanzamento del livello di completamento del lavoro.
Chi ha familiarità con i thread, saprà che se si tenta di accedere a qualsiasi controllo dell'applicazione principale da un thread
separato, viene generata un'eccezione di tipo CrossThreadException. Anche sotto questo punto di vista BackgroundWorker fornisce un aiuto
non di poco conto poichè i suoi eventi sono predisposti in modo tale da evitare errori di questo tipo. Infatti DoWork viene effettivamente
eseguito in un diverso contesto, ma gli eventi sono prodotti nel thread principale, in modo da poter accedere a qualsiasi controllo senza
problemi. Ecco un esempio commentato.
L'interfaccia dovrebbe presentarsi come quella che segue. I nomi sono facilmente intuibili dal sorgente. Bisogna invece aggiungere, ovviamente,
il controllo BackgroundWorker (che non ha interfaccia), con WorkerReportProgress = True e WorkerSupportCancellation = True. Io l'ho chiamato
bgwScan.
E il codice:
Imports System.ComponentModel 'In System.ComponentModel Class Form1 Private Sub txtDir_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles txtDir.Click Dim Open As New FolderBrowserDialog Open.Description = "Scegliere la cartella da analizzare:" If Open.ShowDialog = Windows.Forms.DialogResult.OK Then txtDir.Text = Open.SelectedPath End If End Sub Private Sub cmdAnalyze_Click(ByVal sender As Object, _ ByVal e As EventArgs) Handles cmdAnalyze.Click If cmdAnalyze.Text = "Analizza" Then 'Controlla che la cartella esista If Not IO.Directory.Exists(txtDir.Text) Then MessageBox.Show("Cartella inesistente!", "Sizing", _ MessageBoxButtons.OK, MessageBoxIcon.Exclamation) End If 'Fa partire il BackgroundWorker, passandogli come unico 'argomento il percorso della cartella da analizzare bgwScan.RunWorkerAsync(txtDir.Text) 'Modifica il testo del pulsante, così da potergli 'assegnare anche un altro compito cmdAnalyze.Text = "Ferma" Else 'Il testo non è "Analizza". Deve per forza essere '"Ferma", quindi termina l'operazione forzatamente bgwScan.CancelAsync() cmdAnalyze.Text = "Analizza" End If End Sub Private Sub bgwScan_DoWork(ByVal sender As Object, _ ByVal e As DoWorkEventArgs) Handles bgwScan.DoWork 'Ottiene tutti i files presenti nella cartella: '- e.Argument ottiene lo stesso valore passato a ' RunWorkerAsync: in questo caso contiene una stringa '- il pattern *.* specifica di cercare files di ogni ' estensione '- l'ultimo argomento comunica di eseguire una ricerca ' ricorsiva analizzando anche tutte le sottocartelle Dim Files() As String = _ IO.Directory.GetFiles(e.Argument, "*.*", _ IO.SearchOption.AllDirectories) 'Dimensione totale Dim Size As Double = 0 'Files analizzati Dim Index As Int32 = 0 'Calcola la dimensiona totale della cartella sommando tutte 'le dimensioni parziali For Each File As String In Files 'FileLen è una funzione di VB6, ma il VB.NET 'implicherebbe di creare un nuovo oggetto FileInfo e 'quindi richiamarne la proprietà Length. In 'questo modo è molto più comodo, anche 'se non proprio conforme alle direttive .NET Size += FileLen(File) Index += 1 'Riporta la percentuale e genera un evento 'ProgressChanged bgwScan.ReportProgress(Index * 100 / Files.Length) 'Controlla se ci sono richieste di cancellazione. 'Se ce ne sono, termina qui la procedura If bgwScan.CancellationPending Then e.Cancel = True Exit Sub End If Next 'Il valore Result di e rappresenta il valore da restituire. 'In questo caso è come se DoWork fosse una funzione. 'Dato che si può passare solo un valore Object, 'mettiamo in quel valore un array di Double contenente 'il numero di files trovati e la loro dimensione complessiva e.Result = New Double() {Files.Length, Size} End Sub Private Sub bgwScan_ProgressChanged(ByVal sender As Object, _ ByVal e As ProgressChangedEventArgs) Handles bgwScan.ProgressChanged 'Visualizza la percentuale sulla barra prgProgress.Value = e.ProgressPercentage End Sub Private Sub bgwScan_RunWorkerCompleted(ByVal sender As Object, _ ByVal e As RunWorkerCompletedEventArgs) Handles bgwScan.RunWorkerCompleted 'Controlla la causa che ha scatenato questo evento If e.Cancelled Then 'Una cancellazione? MessageBox.Show("Operazione annullata!", "Sizing", _ MessageBoxButtons.OK, MessageBoxIcon.Exclamation) ElseIf e.Error IsNot Nothing Then 'Un'eccezione? MessageBox.Show("Si è verificato un errore!", "Sizing", _ MessageBoxButtons.OK, MessageBoxIcon.Error) Else 'O semplicemente la fine delle operazioni 'COnverte il risultato ancora in un array di Double Dim Values() As Double = e.Result lblInfo.Text = String.Format("Sono stati trovati {0} files." & _ "{1}La dimensione totale della cartella è {2:N0} bytes.", _ Values(0), vbCrLf, Values(1)) End If 'Per sicurezza, reimposta il testo del pulsante cmdAnalyze.Text = "Analizza" End Sub End Class
L'oggetto FileSystemWatcher serve per monitorare files o cartelle in modo da sapere in tempo reale quando vengono modificati, cancellati, aperti o spostati, ed eseguire azioni di conseguenza. Si potrebbe, ad esempio, controllare un file speciale e visualizzare un messaggio di warning se l'utente cerca di aprirlo, o chiedere una password, oppure addirittura spegnere il computer (!!). Questo controllo, come si pu&ograv; intuire, non ha interfaccia grafica. Le sue proprietà interessanti sono:
- EnableRisingEvent : determina se il controllo generi gli eventi; dato che il suo utilizzo è basato su questi, impostare a True False tale valore equivale ad "accendere" o "spegnere" il controllo
- Filter : specifica il filtro di monitoraggio e si stanno controllando dei files. Non è altro che l'estensione dei files
- IncludeSubdirectories : determina se includere nel monitoraggio anche le sottocartelle
- NotifyFilter : proprietà enumerata codificata a bit che descrive cosa monitorare. Può assumere questi valori: DirectoryName (cambiamenti nel nome della/delle cartella/e), FileName (cambiamenti nel nome dei files), Attributes (cambiamenti degli attributi di un file), Size (dimensione di file o cartelle), LastAccess (ultimo accesso), LastWrite (ultima modifica), CreationTime (data di creazione) e Security (parametri di sicurezza). I valori possono essere sommati con l'operatore su bit Or
- Path : il percorso del file/cartella da monitorare
Bisogna anche dire, però, che nel 99% dei casi questo controllo fa cilecca... infatti non genera nessun evento anche quando dovrebbe. Misteri del .NET Framework!