Utilisation d'Application.DoEvents ()

272

Peut Application.DoEvents()être utilisé en C #?

Cette fonction est-elle un moyen de permettre à l'interface graphique de rattraper le reste de l'application, de la même manière que le DoEventsfait le VB6 ?

Craig Johnston
la source
35
DoEventsfait partie de Windows Forms, pas du langage C #. En tant que tel, il peut être utilisé à partir de n'importe quel langage .NET. Cependant, il ne doit pas être utilisé à partir de n'importe quel langage .NET.
Stephen Cleary
3
Nous sommes en 2017. Utilisez Async / Await. Voir la fin de la réponse @hansPassant pour plus de détails.
FastAl

Réponses:

468

Hmya, la mystique durable de DoEvents (). Il y a eu énormément de contrecoups contre cela, mais personne n'explique jamais vraiment pourquoi c'est "mauvais". Le même genre de sagesse que «ne mute pas une structure». Euh, pourquoi le runtime et le langage prennent-ils en charge la mutation d'une structure si c'est si mauvais? Même raison: vous vous tirez une balle dans le pied si vous ne le faites pas correctement. Facilement. Et le faire correctement nécessite de savoir exactement ce qu'il fait, ce qui dans le cas de DoEvents () n'est certainement pas facile à maîtriser.

Dès le départ: presque tous les programmes Windows Forms contiennent en fait un appel à DoEvents (). Il est habilement déguisé, mais avec un nom différent: ShowDialog (). C'est DoEvents () qui permet à une boîte de dialogue d'être modale sans geler le reste des fenêtres de l'application.

La plupart des programmeurs veulent utiliser DoEvents pour empêcher leur interface utilisateur de geler lorsqu'ils écrivent leur propre boucle modale. C'est certainement le cas; il envoie des messages Windows et reçoit toutes les demandes de peinture. Le problème est cependant qu'il n'est pas sélectif. Il envoie non seulement des messages de peinture, mais il fournit également tout le reste.

Et il y a un ensemble de notifications qui causent des problèmes. Ils viennent d'environ 3 pieds devant le moniteur. L'utilisateur pourrait par exemple fermer la fenêtre principale pendant l'exécution de la boucle qui appelle DoEvents (). Cela fonctionne, l'interface utilisateur a disparu. Mais votre code ne s'est pas arrêté, il exécute toujours la boucle. C'est mauvais. Très très mauvais.

Il y a plus: l'utilisateur peut cliquer sur le même élément de menu ou bouton qui provoque le démarrage de la même boucle. Vous disposez maintenant de deux boucles imbriquées exécutant DoEvents (), la boucle précédente est suspendue et la nouvelle boucle recommence à zéro. Cela pourrait fonctionner, mais mon garçon, les chances sont minces. Surtout lorsque la boucle imbriquée se termine et que la boucle suspendue reprend, essayant de terminer un travail déjà terminé. Si cela ne bombarde pas avec une exception, alors les données sont sûrement brouillées en enfer.

Retour à ShowDialog (). Il exécute DoEvents (), mais notez qu'il fait autre chose. Il désactive toutes les fenêtres de l'application , à l'exception de la boîte de dialogue. Maintenant que le problème de 3 pieds est résolu, l'utilisateur ne peut rien faire pour perturber la logique. Les modes d'échec de fermeture de fenêtre et de redémarrage sont résolus. Ou pour le dire autrement, il n'y a aucun moyen pour l'utilisateur de faire exécuter votre code de programme dans un ordre différent. Il s'exécutera de manière prévisible, tout comme il l'a fait lorsque vous avez testé votre code. Cela rend les dialogues extrêmement ennuyeux; qui ne déteste pas avoir une boîte de dialogue active et ne pas pouvoir copier et coller quelque chose à partir d'une autre fenêtre? Mais c'est le prix.

C'est ce qu'il faut pour utiliser DoEvents en toute sécurité dans votre code. Définir la propriété Enabled de tous vos formulaires sur false est un moyen rapide et efficace d'éviter les problèmes. Bien sûr, aucun programmeur n'aime vraiment faire cela. Et non. C'est pourquoi vous ne devriez pas utiliser DoEvents (). Vous devez utiliser des threads. Même s'ils vous offrent un arsenal complet de façons de tirer sur votre pied de manière colorée et impénétrable. Mais avec l'avantage que vous ne tirez que de votre propre pied; il ne permet pas (généralement) à l'utilisateur de tirer sur le sien.

Les prochaines versions de C # et VB.NET fourniront un pistolet différent avec les nouveaux mots-clés wait et async. Inspiré en partie par les problèmes causés par DoEvents et les threads, mais en grande partie par la conception de l'API de WinRT qui vous oblige à maintenir votre interface utilisateur à jour pendant qu'une opération asynchrone a lieu. Comme lire dans un fichier.

Hans Passant
la source
1
Mais ce n'est que la pointe de l'iceberg. J'utilise Application.DoEventssans problème jusqu'à ce que j'ajoute des fonctionnalités UDP à mon code, ce qui entraîne le problème décrit ici . J'adorerais savoir s'il y a un moyen de contourner cela avec DoEvents.
darda
c'est une bonne réponse, la seule chose qui me semble manquer est une explication / discussion sur la production d'exécution et la conception thread-safe ou le timelicing en général - mais c'est facilement trois fois plus de texte à nouveau. :)
jheriko
5
Bénéficierait d'une solution plus pratique que généralement «utiliser des threads». Par exemple, le BackgroundWorkercomposant gère les threads pour vous, évitant la plupart des résultats colorés de la prise de vue au pied, et il ne nécessite pas de versions de langage C # à la pointe de la technologie.
Ben Voigt
29

C'est possible, mais c'est un hack.

Voir DoEvents Evil? .

Direct depuis la page MSDN qui thedev référencé:

L'appel de cette méthode entraîne la suspension du thread en cours pendant le traitement de tous les messages de fenêtre en attente. Si un message provoque le déclenchement d'un événement, d'autres zones de votre code d'application peuvent s'exécuter. Cela peut entraîner des comportements inattendus de votre application difficiles à déboguer. Si vous effectuez des opérations ou des calculs qui prennent du temps, il est souvent préférable d'effectuer ces opérations sur un nouveau thread. Pour plus d'informations sur la programmation asynchrone, consultez Vue d'ensemble de la programmation asynchrone.

Microsoft met donc en garde contre son utilisation.

De plus, je le considère comme un hack car son comportement est imprévisible et sujet aux effets secondaires (cela vient de l'expérience en essayant d'utiliser DoEvents au lieu de lancer un nouveau thread ou d'utiliser un travailleur en arrière-plan).

Il n'y a pas de machisme ici - si cela fonctionnait comme une solution robuste, je serais partout. Cependant, essayer d'utiliser DoEvents dans .NET ne m'a fait que souffrir.

RQDQ
la source
1
Il convient de noter que ce message date de 2004, avant .NET 2.0 et a BackgroundWorkeraidé à simplifier la "bonne" façon.
Justin
D'accord. De plus, la bibliothèque de tâches dans .NET 4.0 est également plutôt agréable.
RQDQ
1
Pourquoi serait-ce un hack si Microsoft a fourni une fonction dans le but même pour lequel elle est souvent nécessaire?
Craig Johnston
1
@Craig Johnston - a mis à jour ma réponse pour expliquer plus en détail pourquoi je pense que DoEvents entre dans la catégorie du piratage.
RQDQ
Cet article d'horreur sur le codage l'a appelé "DoEvents spackle". Brillant!
gonzobrains
24

Oui, il existe une méthode DoEvents statique dans la classe Application de l'espace de noms System.Windows.Forms. System.Windows.Forms.Application.DoEvents () peut être utilisé pour traiter les messages en attente dans la file d'attente sur le thread d'interface utilisateur lors de l'exécution d'une tâche de longue durée dans le thread d'interface utilisateur. Cela a l'avantage de rendre l'interface utilisateur plus réactive et non «verrouillée» pendant une longue tâche. Cependant, ce n'est presque toujours PAS la meilleure façon de faire les choses. Selon Microsoft, appeler DoEvents "... entraîne la suspension du thread actuel pendant le traitement de tous les messages de fenêtre en attente." Si un événement est déclenché, il existe un risque de bogues inattendus et intermittents difficiles à localiser. Si vous avez une tâche étendue, il est préférable de le faire dans un thread séparé. L'exécution de longues tâches dans un thread séparé permet de les traiter sans interférer avec l'interface utilisateur en continuant de fonctionner correctement. Regardezici pour plus de détails.

Voici un exemple d'utilisation de DoEvents; notez que Microsoft met également en garde contre son utilisation.

Bill W
la source
13

D'après mon expérience, je conseillerais une grande prudence avec l'utilisation de DoEvents dans .NET. J'ai rencontré des résultats très étranges lors de l'utilisation de DoEvents dans un TabControl contenant DataGridViews. D'un autre côté, si vous n'avez affaire qu'à un petit formulaire avec une barre de progression, cela peut être OK.

L'essentiel est: si vous allez utiliser DoEvents, vous devez le tester soigneusement avant de déployer votre application.

Craig Johnston
la source
Belle réponse, mais si je peux suggérer une solution pour la barre de progression qui va mieux , je dirais, faites votre travail dans un fil séparé, rendez votre indicateur de progression disponible dans une variable volatile et verrouillée et rafraîchissez votre barre de progression à partir d'une minuterie . De cette façon, aucun codeur de maintenance n'est tenté d'ajouter du code lourd à votre boucle.
mg30rg
1
DoEvents ou un équivalent est inévitable si vous avez des processus d'interface utilisateur lourds et que vous ne voulez pas verrouiller l'interface utilisateur. La première option est de ne pas faire de gros morceaux de traitement de l'interface utilisateur, mais cela peut rendre le code moins maintenable. Cependant, attendre Dispatcher.Yield () fait une chose très similaire à DoEvents et peut essentiellement permettre à un processus d'interface utilisateur qui verrouille l'écran d'être créé à toutes fins utiles et asynchrones.
Melbourne Developer
11

Oui.

Cependant, si vous avez besoin d'utiliser Application.DoEvents, c'est principalement une indication d'une mauvaise conception d'application. Peut-être aimeriez-vous plutôt travailler dans un fil distinct?

Frederik Gheysels
la source
3
Et si vous le voulez pour pouvoir tourner et attendre que le travail se termine dans un autre fil?
jheriko
@jheriko Alors vous devriez vraiment essayer async-wait.
mg30rg
5

J'ai vu le commentaire de jheriko ci-dessus et convenais initialement que je ne pouvais pas trouver un moyen d'éviter d'utiliser DoEvents si vous finissiez par tourner votre thread d'interface utilisateur principal en attendant la fin d'un morceau de code asynchrone sur un autre thread. Mais d'après la réponse de Matthias, un simple rafraîchissement d'un petit panneau sur mon interface utilisateur peut remplacer les DoEvents (et éviter un effet secondaire désagréable).

Plus de détails sur mon cas ...

Je faisais ce qui suit (comme suggéré ici ) pour garantir qu'un écran de démarrage de type barre de progression ( Comment afficher une superposition de "chargement" ... ) mis à jour lors d'une commande SQL de longue durée:

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
}

Le mauvais: Pour moi, appeler DoEvents signifiait que des clics de souris se déclenchaient parfois sur des formulaires derrière mon écran de démarrage, même si je le faisais TopMost.

La bonne / réponse: Remplacer la ligne DoEvents par un simple appel à un petit rafraîchissement panneau dans le centre de mon écran d'accueil, FormSplash.Panel1.Refresh(). L'interface utilisateur est bien mise à jour et la bizarrerie de DoEvents que d'autres ont prévenue avait disparu.

TamW
la source
3
L'actualisation ne met cependant pas à jour la fenêtre. Si un utilisateur sélectionne une autre fenêtre sur le bureau, cliquer de nouveau sur votre fenêtre n'aura aucun effet et le système d'exploitation répertoriera votre application comme ne répondant pas. DoEvents () fait bien plus qu'une actualisation, car il interagit avec le système d'exploitation via le système de messagerie.
ThunderGr
4

J'ai vu de nombreuses applications commerciales, utilisant le "DoEvents-Hack". Surtout lorsque le rendu entre en jeu, je vois souvent ceci:

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

Ils connaissent tous le mal de cette méthode. Cependant, ils utilisent le hack, car ils ne connaissent aucune autre solution. Voici quelques approches tirées d'un article de blog de Tom Miller :

  • Définissez votre formulaire pour que tous les dessins se produisent dans WmPaint et effectuez votre rendu à cet endroit. Avant la fin de la méthode OnPaint, assurez-vous de faire ceci.Invalidate (); Cela entraînera le déclenchement immédiat de la méthode OnPaint.
  • P / Invoke dans l'API Win32 et appelez PeekMessage / TranslateMessage / DispatchMessage. (Doevents fait en fait quelque chose de similaire, mais vous pouvez le faire sans les allocations supplémentaires).
  • Écrivez votre propre classe de formulaires qui est un petit wrapper autour de CreateWindowEx, et donnez-vous un contrôle complet sur la boucle de message. -Décidez que la méthode DoEvents fonctionne bien pour vous et respectez-la.
Matthias
la source
3

Les DoEvents permettent à l'utilisateur de cliquer ou de taper et de déclencher d'autres événements, et les threads d'arrière-plan sont une meilleure approche.

Cependant, il existe encore des cas où vous pouvez rencontrer des problèmes qui nécessitent le vidage des messages d'événement. J'ai rencontré un problème où le contrôle RichTextBox ignorait la méthode ScrollToCaret () lorsque le contrôle avait des messages en file d'attente à traiter.

Le code suivant bloque toutes les entrées utilisateur lors de l'exécution de 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
    }
}
cat_in_hat
la source
1
Vous devez toujours bloquer les événements lorsque vous appelez des événements. Sinon, d'autres événements sur votre application se déclencheront en réponse et votre application pourrait commencer à faire deux choses à la fois.
FastAl
2

Application.DoEvents peut créer des problèmes si autre chose que le traitement graphique est placé dans la file d'attente de messages.

Cela peut être utile pour mettre à jour les barres de progression et informer l'utilisateur de la progression dans quelque chose comme la construction et le chargement de MainForm, si cela prend un certain temps.

Dans une application récente que j'ai faite, j'ai utilisé DoEvents pour mettre à jour certaines étiquettes sur un écran de chargement chaque fois qu'un bloc de code est exécuté dans le constructeur de mon MainForm. Le thread d'interface utilisateur était, dans ce cas, occupé à envoyer un e-mail sur un serveur SMTP qui ne prend pas en charge les appels SendAsync (). J'aurais probablement pu créer un thread différent avec les méthodes Begin () et End () et appeler un Send () à partir de leur, mais cette méthode est sujette aux erreurs et je préférerais que le formulaire principal de mon application ne lève pas d'exceptions pendant la construction.

Guest123
la source