View Full Version : [WIN FORM .NET] Eseguire operazioni sul form mentre è in corso una routine
voyager18
26-12-2009, 10:23
Ho un windows form che al click di un pulsante esegue un'operazione un pò lunga. Nel form c'è anche una label che durante la routine vorrei che assumesse valori diversi. Ho provato ad impostare il testo della label ma non viene visualizzato fin quando la routine non finisce di lavorare. C'è un modo per far eseguire il rendering della label mentre la routine sta girando?
Energy++
26-12-2009, 10:32
la tua routine la devi far eseguire su un thread diverso, se no è normale che la gui rimanga bloccata.
Il modo più semplice di fare questo secondo me è utilizzare la classe BackgroundWorker (http://msdn.microsoft.com/it-it/library/8xs8549b.aspx).
Il tutorial è molto chiaro, se comunque hai problemi non esitare a chiedere
Ho un windows form che al click di un pulsante esegue un'operazione un pò lunga. Nel form c'è anche una label che durante la routine vorrei che assumesse valori diversi. Ho provato ad impostare il testo della label ma non viene visualizzato fin quando la routine non finisce di lavorare. C'è un modo per far eseguire il rendering della label mentre la routine sta girando?
Basta che aggiungi un Application.DoEvents() subito dopo la linea di codice in cui modifichi il testo della Label, e dovresti già aver risolto. Con una riga eviti di scomodare multithreading e magari di trovarti anche a gestire operazioni cross-thread, per cosucce come questa... ;)
Non sono tanto d'accordo con l'uso del metodo DoEvents.
E' sicuramente vero che in modo semplice riesci a fare quello che ti serve, ovvero aggiornare la label, ma il form rimane comunque bloccato nel tempo che intercorre tra le due chiamate al metodo DoEvents. Inoltre può essere utile, mentre è in corso questa elaborazione un po' lunga, che l'utente possa fare altre operazioni con il programma, cosa che non è fattibile se l'elaborazione è fatta nel thread della gui.
L'uso di BackgroundWorker o comunque di thread specifici per ogni computazione lunga è sicuramente una scelta un po' più complicata da realizzare ma che sicuramente paga in termini di usabilità, versatilità e performances.
Non sono tanto d'accordo con l'uso del metodo DoEvents.
E' sicuramente vero che in modo semplice riesci a fare quello che ti serve, ovvero aggiornare la label, ma il form rimane comunque bloccato nel tempo che intercorre tra le due chiamate al metodo DoEvents. Inoltre può essere utile, mentre è in corso questa elaborazione un po' lunga, che l'utente possa fare altre operazioni con il programma, cosa che non è fattibile se l'elaborazione è fatta nel thread della gui.
L'uso di BackgroundWorker o comunque di thread specifici per ogni computazione lunga è sicuramente una scelta un po' più complicata da realizzare ma che sicuramente paga in termini di usabilità, versatilità e performances.
Se la richiesta era semplicemente questa : "C'è un modo per far eseguire il rendering della label mentre la routine sta girando", direi che DoEvents è la risposta più adatta, oltre che la più immediata.
Inoltre non è affatto vero, come dici, che l'interazione con la gui non sia fattibile dallo stesso thread. Basta verificare se c'è stata interazione dell'utente con GetInputState :
http://www.dotnet2themax.it/ShowContent.aspx?ID=16617690-b534-4822-a0d3-48e19d3cf6a5
Che poi, in casi più complessi il multithread sia praticamente d'obbligo è un altro discorso, ma per il refresh di una Label mi sembra uno spreco.
Se la richiesta era semplicemente questa : "C'è un modo per far eseguire il rendering della label mentre la routine sta girando", direi che DoEvents è la risposta più adatta, oltre che la più immediata.
Inoltre non è affatto vero, come dici, che l'interazione con la gui non sia fattibile dallo stesso thread.
Potrai si verificare se c'è l'interazione o meno, ma se una istruzione è bloccante (come ad esempio un download sincrono di un file), durante quel tempo la gui è bloccata.
Comunque la domanda l'avevo letta bene, e non discuto che con il metodo DoEvents possa funzionare, ritengo solo che sia una soluzione veloce ma che vale ben poco.
Potrai si verificare se c'è l'interazione o meno, ma se una istruzione è bloccante (come ad esempio un download sincrono di un file), durante quel tempo la gui è bloccata.
Se l'operazione è bloccante e costituita da un'istruzione unica, come un download ( My.Computer.Network.DownloadFile(link, target) ), come fai ad aggiornare una Label ?
Non penso fosse l'intenzione di voyager18.
Potrei sbagliarmi, ma credo si tratti di una sua routine ciclica, da qui la mia risposta. In ogni caso finchè voyager18 non si fa vivo con info più precise, resto dell'idea che DoEvents sia da preferirsi nei casi molto semplici, come penso sia questo.
^TiGeRShArK^
28-12-2009, 15:17
Io sono assolutamente contrario a bloccare il thread della GUI.
Non c'è niente di peggio che possa essere fatto.
In questo modo viene bloccata anche tutta la gestione degli eventi, e il programma si comporta come se fosse in hang, inoltre non credo sia affatto scontato che il rendering della label venga effettuato correttamente, o meglio, quantomeno con java era scritto *esplicitamente* questo, ma ad occhio direi che possa valere anche per le winforms.
Quantomeno, in caso non si voglia usare un backgroundworker, sarebbe opportuno utilizzare dei delegate con BeginInvoke per mandare in background l'esecuzione del metodo bloccante.
inoltre non credo sia affatto scontato che il rendering della label venga effettuato correttamente
Questo è verissimo!
Se l'operazione è bloccante e costituita da un'istruzione unica, come un download ( My.Computer.Network.DownloadFile(link, target) ), come fai ad aggiornare una Label ?
Il mio era solo un esempio per farti capire che anche con un bel DoEvents() e pur sfruttando il metodo che menzionavi sopra otterresti un bel freeze della gui.
Detto questo, io non volevo sindacare sull'istruzione, conosco bene quello che fa, ma ritengo che vada usata il meno possibile per questo genere di cose perchè potrebbe portare a spiacevoli risultati.
Energy++
28-12-2009, 15:53
condivido le risposte degli utenti ^TiGeRShArK^ e CwNd per questo avevo fin da subito proposto il backgroundworker :)
Il mio era solo un esempio per farti capire che anche con un bel DoEvents() e pur sfruttando il metodo che menzionavi sopra otterresti un bel freeze della gui.
1. Ma è proprio questo il punto. Il tuo esempio non calza, in quanto se la tua operazione è un'istruzione sola, il refresh della Label DOVE lo agganci ?
"Anche con il DoEvents" non c'entra un fico. Ovvio che se fai il download con singola istruzione non lo usi, il DoEvents, perchè non sai manco dove metterlo, che razza di discorso è ?!
E nemmeno col tuo multithread riesci a cavare un ragno da un buco in quel caso. Prova a mettere la percentuale del file scaricato in una Label con My.Computer.Network.DownloadFile(), con o senza multithread, e fammi sapere.
2. La tecnica che ho linkato più sopra è valida. L'ho usata personalmente, funziona, e la consiglio. Poi starà a chi ha domandato farne l'uso che crede.
Secondo me chi ha aperto il thread ha il classico problema di routine ciclica ( poi... mi sbaglierò... ). Ad ogni ciclo vorrebbe vedere la Label aggiornarsi ecc... ecc... Punto. Se è davvero tutto qui e non c'è altro, il mio consiglio è : DoEvents.
3. La soluzione targata F. Balena ( che comunque qui è superflua e l'ho tirata in ballo solo per dimostrare che si può fare eccome ciò che sostieni fosse impossibile ) per me è il miglior compromesso tra facilità di implementazione e funzionalità nei casi semplici, ripeto.
C'è chi dice "MAI DoEvents, solo multithread". Opinioni. Secondo me ci sono casi e casi...
Con tutto il rispetto che ho per voi, preferisco dare credito al consiglio di un Balena, che peraltro ho avuto modo di verificare in concreto, anche se in contrasto con quello di 10 di voi messi assieme.
C'è chi dice "MAI DoEvents, solo multithread". Opinioni. Secondo me ci sono casi e casi...
C'è chi dice "non sono capace/non ho voglia di implementarlo" e c'è chi dice "mi sbatto un po' ma il risultato sarà migliore". Non è una questione d'opinione, usare un thread separato è la cosa migliore. Ti dirò di più. Sul nuovissimo Windows Market Place, se un task impiega più di un certo tempo (non ricordo precisamente quanto, comunque intorno ai 500ms) sei obbligato ad usare un thraed apposito. Pena, il tuo programma non verrà approvato per la pubblicazione.
Con tutto il rispetto che ho per voi, preferisco dare credito al consiglio di un Balena, che peraltro ho avuto modo di verificare in concreto, anche se in contrasto con quello di 10 di voi messi assieme
Sei libero di farlo, le mie risposte era solo per farti capire il problema. Non per convincerti ad usare una soluzione piuttosto che un altra.
C'è chi dice "non sono capace/non ho voglia di implementarlo" e c'è chi dice "mi sbatto un po' ma il risultato sarà migliore". Non è una questione d'opinione, usare un thread separato è la cosa migliore.
C'è spesso anche chi dice "Dammi la soluzione migliore con il minor sforzo".
Personalmente ho usato parecchio il threading in .Net, e convengo che in generale sia da preferirsi. Ma non è la soluzione più veloce.
E io voglio che l'utente che ha posto la domanda sappia che esiste anche la soluzione "veloce", che nelle cose semplici è valida e funzionante. Che ci volete fare, ho la testa dura. :p
Ho una Form con un solo pulsante che esegue questo codice :
For i As Integer = 1 To 10000
Label1.Text = i
Next
E' già sufficiente perchè l'utente veda solo 0 ( testo Label a design ) e 10000 e nessuno dei valori intermedi.
E di casi simili a questo, certo ben più significativi, ne ho visti a centinaia.
Che faccio, consiglio il multithreading + relativa gestione del cross-thread sulla Form, come prima cosa ?
Tu dirai : SI.
Io invece dico : vediamo se è davvero una cosa veloce, al threading si fa sempre a tempo, semmai.
Quindi è QUI l'opinione, semmai, NON sul fatto che il multithreading sia o meno la scelta più corretta.
For i As Integer = 1 To 10000
Label1.Text = i
My.Application.DoEvents()
Next
e con 1 riga hai risolto.
Più performante o meno, dato che poi comunque sta benedetta Label la devi aggiornare dall'altro Thread ? Forse un pochino... Comunque sia questa è la soluzione in assoluto più semplice, adatta ai casi semplici.
Cosa volete che vi dica : se DoEvents non vi piace, fate una petizione al Team di VS perchè venga deprecato. :p
voyager18
28-12-2009, 18:28
Se l'operazione è bloccante e costituita da un'istruzione unica, come un download ( My.Computer.Network.DownloadFile(link, target) ), come fai ad aggiornare una Label ?
Non penso fosse l'intenzione di voyager18.
Potrei sbagliarmi, ma credo si tratti di una sua routine ciclica, da qui la mia risposta.
Il mio problema non è con una routine ciclica ma al clic di un pulsante eseguo tre istruzioni (una volta soltanto) che richiedono parecchio tempo perchè attendono la risposta da un Web Service (a volte anche 3-4 minuti).
Prova a fare una semplice
Thread.Sleep(3000)
dentro il codice.
La tua label sicuramente verrà aggiornata, ma la gui non risponderà agli eventi.
Il mio problema non è con una routine ciclica ma al clic di un pulsante eseguo tre istruzioni (una volta soltanto) che richiedono parecchio tempo perchè attendono la risposta da un Web Service (a volte anche 3-4 minuti).
Nel caso possono essere così lunghe ti suggerisco di usare i metodi asincroni per fare le chiamate. Se hai generato il proxy per il webservice tramite svcutils.exe c'è un opzione per far generare le classi direttamente con qui metodi. Da visual studio basta che quando fai "aggiungi riferimento al servizio" vai su "avanzate" e spunti la casella "Genera operazioni asincrone". Usandoli sicuramente non hai problemi di freezing della gui e potrai farci qualsiasi operazione.
^TiGeRShArK^
28-12-2009, 18:55
Il mio problema non è con una routine ciclica ma al clic di un pulsante eseguo tre istruzioni (una volta soltanto) che richiedono parecchio tempo perchè attendono la risposta da un Web Service (a volte anche 3-4 minuti).
E io direi di non bloccare il thread di gestione degli eventi durante l'esecuzione della tua applicazione perchè il tuo programma resterà bloccato, e l'effetto per l'utente finale è semplicemente quanto di peggio possa esserci imho.
Praticamente ad ogni istruzione resterà bloccato tutto aggiornandosi solo quando verrà chiamata l'Application.DoEvents() tra un'istruzione e l'altra.
La soluzione + completa per me è il backgroundworker, ma volendo anche il begininvoke col callback può andare bene, dipende dalle situazioni...
Il mio problema non è con una routine ciclica ma al clic di un pulsante eseguo tre istruzioni (una volta soltanto) che richiedono parecchio tempo perchè attendono la risposta da un Web Service (a volte anche 3-4 minuti).
Ecco, se lo dicevi subito mi risparmiavo sta noiosa mini-polemica. :D
E la Label quando la vuoi refreshare ?
E magari postare un po' di codice ?
Però mi raccomando, sempre informazioni col contagocce, sennò è troppo facile. :p
^TiGeRShArK^
28-12-2009, 21:06
Ecco, se lo dicevi subito mi risparmiavo sta noiosa mini-polemica. :D
E la Label quando la vuoi refreshare ?
E magari postare un po' di codice ?
Però mi raccomando, sempre informazioni col contagocce, sennò è troppo facile. :p
:asd:
in effetti... :asd:
voyager18
28-12-2009, 22:08
Ho provato con il background worker e funziona! Grazie a tutti!! :)
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.