Sincronizza Indice |
Scarica il progetto |
Testo dell'articolo |
Stampa l'articolo |
Nello sviluppo di programmi di medie dimensioni si può rendere necessario lanciare una funzione che lavori "per i fatti suoi" e non blocchi l'esecuzione del codice fintanto che essa non è terminata.
Esistono due soluzioni per effettuare quest'operazione: la prima consiste
nell'utilizzare un Timerinsieme
all'istruzione DoEvents. Il timer si dovrebbe occupare di lanciare
la funzione ad intervalli regolari e l'istruzione DoEvents serve per permettere
l'esecuzione di altri eventi mentre la funzione è in esecuzione. L'altra soluzione consiste nel creare, come avviene in altri linguaggi,
dei threads che si occupino di
eseguire la funzione in maniera asincrona,
non dipendente dal resto del programma. Sarà il sistema operativo
che si occuperà di controllare il thread e il programmatore non
si dovrà più occupare d'esso fino a quando non intende modificarlo,
rallentarlo, velocizzarlo o terminarlo. Tuttavia, Visual Basic non permette nativamente l'utilizzo dei threads;
sarà pertanto necessario utilizzare alcune funzioni API
per creare e controllare il thread. Pertanto, prima di vedere un esempio specifico, daremo uno sguardo alla classe clsThreads.
Alla riga 3 definiamo un nuovo tipo di dati definito dall'utente denominato udtThread. Al suo interno abbiamo due campi: Handle di tipo Long che conterrà l'handle del thread comandato dalla classe ed Enabled che indicherà se il thread è in esecuzione. Alla riga 8 dichiariamo la variabile uThread di tipo udtThread. Tale variabile servirà per contenere i dati relativi al thread legato all'istanza.
La riga 10 definisce la costante
CREATE_SUSPENDED
che verrà utilizzata per creare un thread inizialmente fermo, non
in esecuzione.
Le funzioni API definite qui, servono per creare, cambiare la priorità, sospendere, ripristinare e terminare threads. La prima funzione è la CreateThread e crea un nuovo thread che esegua il codice indicato dal puntatore alla funzione lpStartAddress. Le caratteristiche del thread saranno definite dal parametro dwCreationFlags. All'uscita la funzione riporta l'hande del thread creato. La funzione SetThreadPriority permette di cambiare la priorità di un thread, mentre la GetThreadPriority viene utilizzata per ottenere la priorità del thread specificato.
La funzione SuspendThread serve per interrompere - ma non terminare - l'esecuzione di un thread. Analogamente, la ResumeThread ripristina la normale esecuzione del thread dopo la sua sospensione. In ultima analisi, la funzione TerminateThread termina l'esecuzione di un thread e ne distrugge l'handle.
Il metodo principale di questa classe è Initialize. Esso richiede un puntatore a funzione come parametro. Esso verrà utilizzato per creare il thread corrispondente. Il cuore di questa funzione si trova alle righe 35 e 36. Inizialmente definiamo le modalità di creazione del thread. Nel nostro caso il thread verrà creato in maniera sospesa. Alla riga 36 avviene la creazione del thread mediante chiamata alla funzione API CreateThread. Ad essa verranno passati vari parametri, molti dei quali impostati a 0. Il parametro principale è il puntatore alla funzione ricevuto dalla chiamata del metodo Initialize. Se la creazione del thread è avvenuta in maniera corretta, il campo Handle della struttura uThread conterrà l'handle del thread creato. Se esso dovesse essere 0, il thread non sarà stato creato e sarà mostrato un messaggio di errore (riga 37). Seguono un paio di proprietà che consentono di controllare l'esecuzione del thread creato dall'istanza della classe.
La proprietà Enabled è utilizzabile in lettura ed in scrittura
(Get e Let). La lettura della proprietà avviene semplicemente leggendo il valore dal campo Enabled della struttura uThread. La scrittura della proprietà comporta invece l'attivazione o la sospensione del thread. Dopo quest'operazione sarà necessario aggiornare il contenuto del campo Enabled di uThread.
Anche la proprietà Priority è in lettura e scrittura. La lettura utilizza la chiamata alla funzione GetThreadPriority; la scrittura invece utilizza la funzione SetThreadPriority per cambiare la priorità del thread in esecuzione.
La deallocazione dell'istanza comporta la sua distruzione e l'esecuzione dell'evento Terminate. Pertanto all'esecuzione di tale evento sarà necessario distruggere il thread creato e questo viene effettuato mediante la funzione TerminateThread. Possiamo vedere l'implementazione della classe appena sviluppata. Inseriamo all'interno di un form due PictureBox di nome Picture1 e Picture2, settando per esse due colori differenti nella proprietà BackColor. Inseriamo anche un CommandButtondi nome AvviaThreads. Abbiamo inserito anche una Label descrittiva ma essa non è effettivamente necessaria. Il codice si compone esclusivamente di una routine: il click sul pulsante AvviaThreads.
Alla righe 4 e 5 definiamo ed istanziamo due oggetti di classe clsThreads. Alla riga 7 creiamo il primo thread richiamando il metodo Initialize. Passeremo alla funzione il puntatore alla funzione FlickerTop (che per forza di cose deve risiedere all'interno di un modulo standard), che vedremo a breve. Creato il thread lo avviamo impostando la proprietà Enabled dell'istanza myThreadTop.
Operazione simile viene eseguita per la seconda istanza, myThreadBottom. Inizializziamo il thread con il metodo Initialize e il puntatore alla funzione FlickerBottom e lo avviamo impostando la proprietà Enabled. Per dimostrare l'effettiva attività dei threads richiamiamo una
MessageBox (riga 13) che in situazioni normali blocca l'intera esecuzione
del programma. Alle righe 14 e 15 vengono deallocate e distrutte le due istanze. Così facendo saranno terminati i thread lanciati. Prima di provare il programma dobbiamo definire le due funzioni da eseguire
all'interno dei threads. Le due funzioni devono stare necessariamente
all'interno di un modulo
standard poiché l'operatore AddressOf che recupera l'indirizzo
di una funzione richiede che la funzione risieda all'interno di un modulo
standard.
Le due funzioni utilizzano la funzione API GetTickCount, vista peraltro in un altro HowTo, per effettuare un'attesa. La prima funzione è la FlickerTop e provvede a cambiare il colore di sfondo di Picture1 del form. Utilizzeremo una variabile statica, in maniera che all'uscita della funzione non venga azzerato il suo contenuto, di nome BgColor. Alla riga 7 inizia un ciclo che non terminerà mai, ovvero sarà eseguito fintanto che 1 è maggiore di 0, ovvero a tempo indeterminato. Alla riga 8 viene verificato il valore di BgColor: se esso è diverso
da &HFF& (colore Rosso), sarà posto uguale a &HFF&,
altrimenti sarà posto uguale a &HFF00& (verde). Alla riga 11 viene ottenuto il numero di millisecondi dall'avvio di Windows. Segue un ciclo che si ripete fintanto che non siano passati 1250 millisecondi dall'ultimo cambio di colore; rappresenta una maniera alternativa per effettuare un'attesa. Il ciclo si ripete infinitamente e prima di cambiare il colore attende 1250 millisecondi. Ad ogni fase imposta il colore in Rosso o Verde.
La funzione FlickerBottom esegue un'operazione del tutto identica alla precedente, eccetto per il fatto che i colori saranno &HFFFF& (Giallo) e &HFF0000& (Blu). Il colore sarà impostato nella Picture2 ogni 500 millisecondi. Sarà adesso possibile eseguire il programma, premere il pulsante e vedere i due threads lavorare, senza essere interrotti dalla MessageBox con il messaggio. Ad intervalli regolari il colore delle due PictureBox sarà cambiato. |
La soluzione proposta è molto geniale, ma non molto robusta. In altri linguaggi quali il Java l'operazione sarebbe notevolmente più semplice; purtroppo Visual Basic non contiene alcuna istruzione per permettere il richiamo o la gestione di threads multipli. Attenzione! Codice
originale di Peter Larsson
|
Torna all'indice degli HowTo |