Utilizzo di Application.DoEvents ()


272

Può Application.DoEvents()essere usato in C #?

Questa funzione è un modo per consentire alla GUI di mettersi al passo con il resto dell'app, proprio come fa VB6 DoEvents?


35
DoEventsfa parte di Windows Form, non il linguaggio C #. Come tale, può essere utilizzato da qualsiasi linguaggio .NET. Tuttavia, non dovrebbe essere utilizzato da alcun linguaggio .NET.
Stephen Cleary,

3
È il 2017. Usa Async / Await. Vedi la fine della risposta di @hansPassant per i dettagli.
FastAl

Risposte:


468

Hmya, la mistica duratura di DoEvents (). C'è stata un'enorme quantità di contraccolpo, ma nessuno spiega mai perché sia ​​"cattivo". Lo stesso tipo di saggezza di "non mutare una struttura". Ehm, perché il runtime e il linguaggio supportano la mutazione di una struttura se è così male? Stesso motivo: ti spari in un piede se non lo fai nel modo giusto. Facilmente. E farlo bene richiede sapere esattamente cosa fa, cosa che nel caso di DoEvents () non è sicuramente facile da capire.

Fin dall'inizio: quasi tutti i programmi Windows Form contengono effettivamente una chiamata a DoEvents (). È abilmente mascherato, tuttavia con un nome diverso: ShowDialog (). È DoEvents () che consente a una finestra di dialogo di essere modale senza congelare il resto delle finestre nell'applicazione.

La maggior parte dei programmatori desidera utilizzare DoEvents per bloccare il congelamento della propria interfaccia utente quando scrive il proprio loop modale. Lo fa certamente; invia messaggi di Windows e riceve tutte le richieste di disegno. Il problema tuttavia è che non è selettivo. Non solo invia messaggi di disegno, ma offre anche tutto il resto.

E c'è una serie di notifiche che causano problemi. Provengono da circa 3 piedi davanti al monitor. Ad esempio, l'utente potrebbe chiudere la finestra principale mentre il ciclo che chiama DoEvents () è in esecuzione. Funziona, l'interfaccia utente non c'è più. Ma il tuo codice non si è fermato, sta ancora eseguendo il ciclo. Questo è male. Molto molto male.

C'è di più: l'utente può fare clic sulla stessa voce di menu o pulsante che fa iniziare lo stesso loop. Ora hai due loop nidificati che eseguono DoEvents (), il loop precedente viene sospeso e il nuovo loop inizia da zero. Potrebbe funzionare, ma ragazzo le probabilità sono scarse. Soprattutto quando termina il ciclo nidificato e riprende quello sospeso, cercando di terminare un lavoro che era già stato completato. Se ciò non bomba con un'eccezione, allora sicuramente i dati vengono rimescolati all'inferno.

Torna a ShowDialog (). Esegue DoEvents (), ma nota che fa qualcos'altro. Si disattiva tutte le finestre dell'applicazione , ad eccezione della finestra di dialogo. Ora che il problema di 3 piedi è stato risolto, l'utente non può fare nulla per confondere la logica. Vengono risolte le modalità di errore Chiudi finestra e Riavvia il processo. O, per dirla in altro modo, l'utente non ha modo di far eseguire il codice del programma in un ordine diverso. Verrà eseguito in modo prevedibile, proprio come quando hai testato il tuo codice. Rende i dialoghi estremamente fastidiosi; chi non odia avere una finestra di dialogo attiva e non essere in grado di copiare e incollare qualcosa da un'altra finestra? Ma questo è il prezzo.

Qual è ciò che serve per usare DoEvents in modo sicuro nel tuo codice. L'impostazione della proprietà Enabled di tutti i moduli su false è un modo rapido ed efficace per evitare problemi. Ovviamente, a nessun programmatore è mai piaciuto farlo. E non lo fa. Ecco perché non dovresti usare DoEvents (). Dovresti usare le discussioni. Anche se ti consegnano un arsenale completo di modi per sparare al tuo piede in modi colorati e imperscrutabili. Ma con il vantaggio di sparare solo al tuo piede; non permetterà (in genere) all'utente di sparare ai suoi.

Le prossime versioni di C # e VB.NET forniranno una pistola diversa con le nuove parole chiave wait e async. Ispirato in piccola parte dai problemi causati da DoEvents e thread, ma in gran parte dalla progettazione dell'API di WinRT che richiede di mantenere aggiornata l'interfaccia utente mentre è in corso un'operazione asincrona. Come leggere da un file.


1
Questa è solo la punta del berg, però. Ho usato Application.DoEventssenza problemi fino a quando non ho aggiunto alcune funzionalità UDP al mio codice, il che ha comportato il problema descritto qui . Mi piacerebbe sapere se c'è un modo per aggirare che con DoEvents.
darda

questa è una bella risposta, l'unica cosa che mi manca è una spiegazione / discussione sulla resa dell'esecuzione e sulla progettazione thread-safe o sul timeslicing in generale - ma questo è facilmente tre volte più testo. :)
jheriko il

5
Trarrebbe vantaggio da una soluzione più pratica di quella generalmente "usa i thread". Ad esempio, il BackgroundWorkercomponente gestisce i thread per te, evitando la maggior parte dei risultati colorati del foot-shooting e non richiede versioni in linguaggio C # all'avanguardia.
Ben Voigt,

29

Può essere, ma è un trucco.

Vedi DoEvents Evil? .

Direttamente dalla pagina MSDN a cui faceva riferimento:

La chiamata a questo metodo provoca la sospensione del thread corrente mentre vengono elaborati tutti i messaggi della finestra di attesa. Se un messaggio provoca l'attivazione di un evento, è possibile che vengano eseguite altre aree del codice dell'applicazione. Ciò può causare alla tua applicazione comportamenti inaspettati difficili da eseguire il debug. Se si eseguono operazioni o calcoli che richiedono molto tempo, è spesso preferibile eseguire tali operazioni su un nuovo thread. Per ulteriori informazioni sulla programmazione asincrona, vedere Panoramica sulla programmazione asincrona.

Quindi Microsoft mette in guardia contro il suo utilizzo.

Inoltre, lo considero un hack perché il suo comportamento è imprevedibile e soggetto a effetti collaterali (questo proviene dall'esperienza che prova a usare DoEvents invece di girare un nuovo thread o usare background worker).

Non c'è machismo qui - se funzionasse come una soluzione robusta lo farei dappertutto. Tuttavia, provare a usare DoEvents in .NET non mi ha causato altro che dolore.


1
Vale la pena notare che quel post è del 2004, prima di .NET 2.0 e ha BackgroundWorkercontribuito a semplificare il modo "corretto".
Giustino

Concordato. Inoltre, anche la libreria Task in .NET 4.0 è piuttosto carina.
RQDQ

1
Perché sarebbe un trucco se Microsoft avesse fornito una funzione proprio per lo scopo per cui è spesso necessaria?
Craig Johnston,

1
@Craig Johnston - ho aggiornato la mia risposta in modo più dettagliato perché ritengo che DoEvents rientri nella categoria degli hacker.
RQDQ,

Quell'articolo horror in codice lo chiamava "DoEvents spackle". Brillante!
gonzobrains,

24

Sì, esiste un metodo DoEvents statico nella classe Application nello spazio dei nomi System.Windows.Forms. System.Windows.Forms.Application.DoEvents () può essere utilizzato per elaborare i messaggi in attesa nella coda sul thread dell'interfaccia utente quando si esegue un'attività di lunga durata nel thread dell'interfaccia utente. Ciò ha il vantaggio di rendere l'interfaccia utente più reattiva e non "bloccata" mentre è in esecuzione un'attività lunga. Tuttavia, questo NON è quasi sempre il modo migliore per fare le cose. Secondo Microsoft che chiama DoEvents "... causa la sospensione del thread corrente mentre vengono elaborati tutti i messaggi della finestra di attesa." Se viene attivato un evento, è possibile che si verifichino bug imprevisti e intermittenti difficili da rintracciare. Se hai un compito esteso è molto meglio farlo in un thread separato. L'esecuzione di attività lunghe in un thread separato consente di elaborarle senza interferire con l'interfaccia utente continuando a funzionare senza problemi. Guardaqui per maggiori dettagli.

Ecco un esempio di come usare DoEvents; si noti che Microsoft fornisce anche una precauzione contro l'utilizzo.


13

Dalla mia esperienza, consiglierei con molta cautela l'uso di DoEvents in .NET. Ho riscontrato risultati molto strani durante l'utilizzo di DoEvents in un TabControl contenente DataGridViews. D'altra parte, se tutto ciò che hai a che fare è un piccolo modulo con una barra di avanzamento, potrebbe essere OK.

La linea di fondo è: se si intende utilizzare DoEvents, è necessario testarlo accuratamente prima di distribuire l'applicazione.


Bella risposta, ma se potessi suggerire una soluzione per la barra di avanzamento che è meglio , direi, fai il tuo lavoro in un thread separato, rendi l'indicatore di progresso disponibile in una variabile instabile e interbloccata e aggiorna la barra di avanzamento da un timer . In questo modo nessun programmatore di manutenzione è tentato di aggiungere codice pesante al proprio loop.
mg30rg

1
DoEvents o un equivalente è inevitabile se si hanno processi UI pesanti e non si desidera bloccare l'interfaccia utente. La prima opzione è di non eseguire grandi blocchi di elaborazione dell'interfaccia utente, ma ciò può rendere il codice meno gestibile. Tuttavia, wait Dispatcher.Yield () fa una cosa molto simile a DoEvents e può essenzialmente consentire un processo UI che blocca lo schermo per essere asincrono a tutti gli effetti.
Melbourne Developer

11

Sì.

Tuttavia, se è necessario utilizzarlo Application.DoEvents, questo è principalmente un'indicazione di una cattiva progettazione dell'applicazione. Forse ti piacerebbe fare un po 'di lavoro in un thread separato?


3
cosa succede se lo si desidera in modo da poter girare e attendere il completamento del lavoro in un altro thread?
jheriko,

@jheriko Allora dovresti davvero provare async-waitit.
mg30rg

5

Ho visto il commento di jheriko sopra e inizialmente ero d'accordo sul fatto che non avrei potuto trovare un modo per evitare di usare DoEvents se finissi per girare il tuo thread dell'interfaccia utente principale in attesa di un pezzo di codice asincrono a esecuzione prolungata su un altro thread da completare. Ma dalla risposta di Matthias un semplice aggiornamento di un piccolo pannello sulla mia interfaccia utente può sostituire i DoEvent (ed evitare un brutto effetto collaterale).

Maggiori dettagli sul mio caso ...

Stavo facendo quanto segue (come suggerito qui ) per assicurarmi che una schermata iniziale di tipo barra di avanzamento ( Come visualizzare un overlay "caricamento" ... ) aggiornata durante un comando SQL a esecuzione prolungata:

IAsyncResult asyncResult = sqlCmd.BeginExecuteNonQuery();
while (!asyncResult.IsCompleted)  //UI thread needs to Wait for Async SQL command to return
{
      System.Threading.Thread.Sleep(10); 
      Application.DoEvents();  //to make the UI responsive
}

Il cattivo: per me chiamare DoEvents significava che a volte i clic del mouse si attivavano sui moduli dietro la mia schermata iniziale, anche se l'ho fatto TopMost.

La buona / risposta: Sostituire la linea DoEvents con una semplice chiamata Aggiorna per un piccolo pannello al centro della mia splash screen, FormSplash.Panel1.Refresh(). L'interfaccia utente si aggiorna bene e la stranezza di DoEvents che altri hanno avvertito era sparita.


3
Aggiorna, tuttavia, non aggiorna la finestra. Se un utente seleziona un'altra finestra sul desktop, fare clic di nuovo sulla finestra non avrà alcun effetto e il sistema operativo elencherà l'applicazione come non rispondente. DoEvents () fa molto più di un aggiornamento, poiché interagisce con il sistema operativo attraverso il sistema di messaggistica.
ThunderGr

4

Ho visto molte applicazioni commerciali, usando "DoEvents-Hack". Soprattutto quando entra in gioco il rendering, vedo spesso questo:

while(running)
{
    Render();
    Application.DoEvents();
}

Sanno tutti del male di quel metodo. Tuttavia, usano l'hack, perché non conoscono altre soluzioni. Ecco alcuni approcci tratti da un post sul blog di Tom Miller :

  • Imposta il modulo in modo che tutti i disegni vengano eseguiti in WmPaint e esegui il rendering lì. Prima della fine del metodo OnPaint, assicurati di eseguire this.Invalidate (); Ciò causerà l'attivazione immediata del metodo OnPaint.
  • P / Richiama nell'API Win32 e chiama PeekMessage / TranslateMessage / DispatchMessage. (Doevents in realtà fa qualcosa di simile, ma puoi farlo senza le allocazioni extra).
  • Scrivi la tua classe di moduli che è un piccolo wrapper attorno a CreateWindowEx e datti il ​​controllo completo sul ciclo dei messaggi. -Decidi che il metodo DoEvents funziona bene per te e seguilo.


3

DoEvents consente all'utente di fare clic o digitare e attivare altri eventi e i thread in background rappresentano un approccio migliore.

Tuttavia, ci sono ancora casi in cui potresti imbatterti in problemi che richiedono lo scaricamento di messaggi di eventi. Ho riscontrato un problema in cui il controllo RichTextBox stava ignorando il metodo ScrollToCaret () quando il controllo aveva i messaggi in coda per l'elaborazione.

Il codice seguente blocca tutti gli input dell'utente durante l'esecuzione di DoEvents:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Integrative.Desktop.Common
{
    static class NativeMethods
    {
        #region Block input

        [DllImport("user32.dll", EntryPoint = "BlockInput")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool BlockInput([MarshalAs(UnmanagedType.Bool)] bool fBlockIt);

        public static void HoldUser()
        {
            BlockInput(true);
        }

        public static void ReleaseUser()
        {
            BlockInput(false);
        }

        public static void DoEventsBlockingInput()
        {
            HoldUser();
            Application.DoEvents();
            ReleaseUser();
        }

        #endregion
    }
}

1
Dovresti sempre bloccare gli eventi quando chiami eventi. Altrimenti altri eventi sulla tua app verranno generati in risposta e la tua app potrebbe iniziare a fare due cose contemporaneamente.
FastAl

2

Application.DoEvents può creare problemi se nella coda dei messaggi viene inserito qualcosa di diverso dall'elaborazione grafica.

Può essere utile per aggiornare le barre di avanzamento e avvisare l'utente dell'avanzamento in qualcosa come la costruzione e il caricamento di MainForm, se ciò richiede un po 'di tempo.

In una recente applicazione che ho fatto, ho usato DoEvents per aggiornare alcune etichette su una schermata di caricamento ogni volta che viene eseguito un blocco di codice nel costruttore del mio MainForm. Il thread dell'interfaccia utente era, in questo caso, occupato con l'invio di un'e-mail su un server SMTP che non supportava le chiamate SendAsync (). Probabilmente avrei potuto creare un thread diverso con i metodi Begin () e End () e chiamare Send () dai loro, ma quel metodo è soggetto a errori e preferirei che la forma principale della mia applicazione non generasse eccezioni durante la costruzione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.