Verwendung von Application.DoEvents ()


272

Kann Application.DoEvents()in C # verwendet werden?

Ist diese Funktion eine Möglichkeit, der GUI zu ermöglichen, mit dem Rest der App Schritt zu halten, ähnlich wie dies bei VB6 der DoEventsFall ist?


35
DoEventsist Teil von Windows Forms, nicht der C # -Sprache. Als solches kann es aus jeder .NET-Sprache verwendet werden. Es sollte jedoch nicht aus einer .NET-Sprache verwendet werden.
Stephen Cleary

3
Es ist 2017. Verwenden Sie Async / Await. Weitere Informationen finden Sie am Ende der @ hansPassant-Antwort.
FastAl

Antworten:


468

Hmya, die dauerhafte Mystik von DoEvents (). Es gab eine enorme Menge an Gegenreaktionen, aber niemand erklärt jemals wirklich, warum es "schlecht" ist. Die gleiche Art von Weisheit wie "Mutiere keine Struktur". Ähm, warum unterstützen die Laufzeit und die Sprache das Mutieren einer Struktur, wenn das so schlecht ist? Gleicher Grund: Sie schießen sich in den Fuß, wenn Sie es nicht richtig machen. Leicht. Und um es richtig zu machen, muss man genau wissen , was es tut, was im Fall von DoEvents () definitiv nicht einfach zu verstehen ist.

Auf Anhieb: Fast jedes Windows Forms-Programm enthält einen Aufruf von DoEvents (). Es ist geschickt getarnt, jedoch mit einem anderen Namen: ShowDialog (). Es ist DoEvents (), mit dem ein Dialog modal sein kann, ohne dass der Rest der Fenster in der Anwendung eingefroren wird.

Die meisten Programmierer möchten DoEvents verwenden, um zu verhindern, dass ihre Benutzeroberfläche einfriert, wenn sie ihre eigene Modalschleife schreiben. Das macht es sicherlich; Es sendet Windows-Nachrichten und erhält alle Malanforderungen. Das Problem ist jedoch, dass es nicht selektiv ist. Es versendet nicht nur Farbnachrichten, sondern liefert auch alles andere.

Und es gibt eine Reihe von Benachrichtigungen, die Probleme verursachen. Sie kommen aus etwa 3 Fuß vor dem Monitor. Der Benutzer könnte beispielsweise das Hauptfenster schließen, während die Schleife ausgeführt wird, die DoEvents () aufruft. Das funktioniert, die Benutzeroberfläche ist weg. Aber Ihr Code hat nicht aufgehört, er führt immer noch die Schleife aus. Das ist schlecht. Sehr sehr schlecht.

Es gibt noch mehr: Der Benutzer kann auf denselben Menüpunkt oder dieselbe Schaltfläche klicken, wodurch dieselbe Schleife gestartet wird. Jetzt haben Sie zwei verschachtelte Schleifen, die DoEvents () ausführen. Die vorherige Schleife wird angehalten und die neue Schleife beginnt von vorne. Das könnte funktionieren, aber Junge, die Chancen stehen schlecht. Insbesondere, wenn die verschachtelte Schleife endet und die angehaltene Schleife fortgesetzt wird und versucht wird, einen bereits abgeschlossenen Job zu beenden. Wenn das mit einer Ausnahme nicht bombardiert, werden die Daten sicherlich zur Hölle verschlüsselt.

Zurück zu ShowDialog (). Es führt DoEvents () aus, aber beachten Sie, dass es etwas anderes tut. Es deaktiviert alle Fenster in der Anwendung außer dem Dialogfeld. Nachdem das 3-Fuß-Problem gelöst ist, kann der Benutzer nichts mehr tun, um die Logik durcheinander zu bringen. Sowohl der Fehlermodus zum Schließen des Fensters als auch zum erneuten Starten des Jobs werden behoben. Oder anders ausgedrückt: Es gibt keine Möglichkeit für den Benutzer, den Code Ihres Programms in einer anderen Reihenfolge auszuführen. Es wird vorhersehbar ausgeführt, genau wie beim Testen Ihres Codes. Es macht Dialoge extrem nervig; Wer hasst es nicht, einen Dialog aktiv zu haben und nicht in der Lage zu sein, etwas aus einem anderen Fenster zu kopieren und einzufügen? Aber das ist der Preis.

Welches ist das, was es braucht, um DoEvents sicher in Ihrem Code zu verwenden. Wenn Sie die Enabled-Eigenschaft aller Ihrer Formulare auf false setzen, können Sie Probleme schnell und effizient vermeiden. Natürlich mag es kein Programmierer jemals, dies zu tun. Und nicht. Deshalb sollten Sie DoEvents () nicht verwenden. Sie sollten Threads verwenden. Auch wenn sie Ihnen ein komplettes Arsenal an Möglichkeiten bieten, Ihren Fuß auf farbenfrohe und unergründliche Weise zu schießen. Aber mit dem Vorteil, dass Sie nur Ihren eigenen Fuß schießen; Der Benutzer kann (normalerweise) nicht auf ihre schießen.

Die nächsten Versionen von C # und VB.NET bieten eine andere Waffe mit den neuen Schlüsselwörtern "Warten" und "Asynchronisieren". Inspiriert zu einem kleinen Teil durch die Probleme verursacht durch DoEvents und Fäden , sondern zu einem großen Teil von WinRT API - Design , das erfordert Sie Ihre UI aktualisiert zu halten , während ein asynchroner Vorgang stattfindet. Wie aus einer Datei lesen.


1
Dies ist jedoch nur die Spitze des Eisbergs. Ich habe Application.DoEventsohne Probleme verwendet, bis ich meinem Code einige UDP-Funktionen hinzugefügt habe, was zu dem hier beschriebenen Problem führte . Ich würde gerne wissen , ob es einen Weg gibt , um das mit DoEvents.
Darda

Dies ist eine nette Antwort. Das einzige, was ich vermisse, ist eine Erklärung / Diskussion, wie man Ausführung und threadsicheres Design oder Timeslicing im Allgemeinen liefert - aber das ist leicht wieder dreimal so viel Text. :)
Jheriko

5
Würde von einer praktischeren Lösung profitieren als allgemein "Threads verwenden". Zum Beispiel BackgroundWorkerverwaltet die Komponente Threads für Sie, um die meisten farbenfrohen Ergebnisse des Fußschießens zu vermeiden, und erfordert keine hochmodernen C # -Sprachenversionen.
Ben Voigt

29

Es kann sein, aber es ist ein Hack.

Siehe Ist DoEvents böse? .

Direkt von der MSDN-Seite , auf die der Entwickler verwiesen hat:

Durch Aufrufen dieser Methode wird der aktuelle Thread angehalten, während alle wartenden Fenstermeldungen verarbeitet werden. Wenn durch eine Nachricht ein Ereignis ausgelöst wird, werden möglicherweise andere Bereiche Ihres Anwendungscodes ausgeführt. Dies kann dazu führen, dass Ihre Anwendung unerwartete Verhaltensweisen aufweist, die schwer zu debuggen sind. Wenn Sie Operationen oder Berechnungen ausführen, die lange dauern, ist es häufig vorzuziehen, diese Operationen für einen neuen Thread auszuführen. Weitere Informationen zur asynchronen Programmierung finden Sie unter Übersicht über die asynchrone Programmierung.

Microsoft warnt daher vor seiner Verwendung.

Ich halte es auch für einen Hack, da sein Verhalten unvorhersehbar und anfällig für Nebenwirkungen ist (dies beruht auf der Erfahrung, DoEvents zu verwenden, anstatt einen neuen Thread zu starten oder einen Hintergrund-Worker zu verwenden).

Hier gibt es keinen Machismo - wenn es als robuste Lösung funktionieren würde, wäre ich total begeistert. Der Versuch, DoEvents in .NET zu verwenden, hat mir jedoch nur Schmerzen bereitet.


1
Es ist erwähnenswert, dass dieser Beitrag aus dem Jahr 2004 vor .NET 2.0 stammt und dazu BackgroundWorkerbeigetragen hat, den "richtigen" Weg zu vereinfachen.
Justin

Einverstanden. Auch die Aufgabenbibliothek in .NET 4.0 ist ziemlich gut.
RQDQ

1
Warum sollte es ein Hack sein, wenn Microsoft eine Funktion für den Zweck bereitgestellt hat, für den sie häufig benötigt wird?
Craig Johnston

1
@Craig Johnston - hat meine Antwort aktualisiert, um detaillierter zu beschreiben, warum DoEvents meiner Meinung nach in die Kategorie Hackery fällt.
RQDQ

Dieser codierende Horrorartikel bezeichnete ihn als "DoEvents spackle". Brillant!
Gonzobrains

24

Ja, es gibt eine statische DoEvents-Methode in der Application-Klasse im System.Windows.Forms-Namespace. Mit System.Windows.Forms.Application.DoEvents () können die in der Warteschlange des UI-Threads wartenden Nachrichten verarbeitet werden, wenn eine lange laufende Aufgabe im UI-Thread ausgeführt wird. Dies hat den Vorteil, dass die Benutzeroberfläche reaktionsschneller und nicht "blockiert" erscheint, während eine lange Aufgabe ausgeführt wird. Dies ist jedoch fast immer NICHT der beste Weg, um Dinge zu tun. Laut Microsoft ruft DoEvents auf "... bewirkt, dass der aktuelle Thread angehalten wird, während alle wartenden Fenstermeldungen verarbeitet werden." Wenn ein Ereignis ausgelöst wird, besteht die Gefahr unerwarteter und zeitweise auftretender Fehler, die nur schwer aufzuspüren sind. Wenn Sie eine umfangreiche Aufgabe haben, ist es weitaus besser, diese in einem separaten Thread auszuführen. Durch das Ausführen langer Aufgaben in einem separaten Thread können diese verarbeitet werden, ohne dass die Benutzeroberfläche weiterhin reibungslos ausgeführt wird. Aussehenhier für weitere Details.

Hier ist ein Beispiel für die Verwendung von DoEvents. Beachten Sie, dass Microsoft auch vor der Verwendung warnt.


13

Aus meiner Erfahrung würde ich bei der Verwendung von DoEvents in .NET mit großer Vorsicht vorgehen. Bei der Verwendung von DoEvents in einem TabControl mit DataGridViews sind einige sehr seltsame Ergebnisse aufgetreten. Wenn Sie sich jedoch nur mit einem kleinen Formular mit einem Fortschrittsbalken befassen, ist dies möglicherweise in Ordnung.

Das Fazit lautet: Wenn Sie DoEvents verwenden möchten, müssen Sie es gründlich testen, bevor Sie Ihre Anwendung bereitstellen.


Gute Antwort, aber wenn ich eine Lösung für die Fortschrittsanzeige vorschlagen darf, die besser ist , würde ich sagen, erledigen Sie Ihre Arbeit in einem separaten Thread, stellen Sie Ihre Fortschrittsanzeige in einer flüchtigen, ineinandergreifenden Variablen zur Verfügung und aktualisieren Sie Ihre Fortschrittsanzeige über einen Timer . Auf diese Weise ist kein Wartungscodierer versucht, Ihrer Schleife Schwergewichtscode hinzuzufügen.
mg30rg

1
DoEvents oder ein gleichwertiges Programm ist unvermeidlich, wenn Sie umfangreiche UI-Prozesse haben und die UI nicht blockieren möchten. Die erste Option besteht darin, keine großen Teile der UI-Verarbeitung durchzuführen. Dies kann jedoch dazu führen, dass der Code weniger wartbar ist. Warten Sie jedoch, bis Dispatcher.Yield () DoEvents sehr ähnlich ist, und ermöglichen Sie im Wesentlichen, dass ein UI-Prozess, der den Bildschirm blockiert, in jeder Hinsicht asynchron ausgeführt wird.
Melbourne Entwickler

11

Ja.

Wenn Sie jedoch verwenden müssen, Application.DoEventsist dies meistens ein Hinweis auf ein schlechtes Anwendungsdesign. Vielleicht möchten Sie stattdessen in einem separaten Thread arbeiten?


3
Was ist, wenn Sie es möchten, damit Sie sich drehen und warten können, bis die Arbeit in einem anderen Thread abgeschlossen ist?
Jheriko

@jheriko Dann solltest du es wirklich mit Async-Warten versuchen.
mg30rg

5

Ich habe Jherikos Kommentar oben gesehen und war mir zunächst einig, dass ich keinen Weg finden konnte, die Verwendung von DoEvents zu vermeiden, wenn Sie am Ende Ihren Haupt-UI-Thread drehen und darauf warten, dass ein lang laufender asynchroner Code in einem anderen Thread abgeschlossen wird. Aber nach Matthias 'Antwort kann eine einfache Aktualisierung eines kleinen Panels auf meiner Benutzeroberfläche die DoEvents ersetzen (und einen bösen Nebeneffekt vermeiden).

Mehr Details zu meinem Fall ...

Ich habe Folgendes getan (wie hier vorgeschlagen ), um sicherzustellen, dass ein Begrüßungsbildschirm vom Typ Fortschrittsbalken angezeigt wird ( Anzeigen eines "Lade" -Overlays ... ) während eines lang laufenden SQL-Befehls aktualisiert wurde:

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
}

Das Schlechte: Für mich bedeutete das Aufrufen von DoEvents, dass manchmal Mausklicks auf Formulare hinter meinem Begrüßungsbildschirm ausgelöst wurden, selbst wenn ich es zu TopMost gemacht hatte.

Das Gute / die Antwort: Ersetzen Sie die DoEvents-Zeile durch einen einfachen Aktualisierungsaufruf für ein kleines Bedienfeld in der Mitte meines Begrüßungsbildschirms FormSplash.Panel1.Refresh(). Die Benutzeroberfläche wird gut aktualisiert und die DoEvents-Verrücktheit, vor der andere gewarnt haben, war verschwunden.


3
Durch Aktualisieren wird das Fenster jedoch nicht aktualisiert. Wenn ein Benutzer ein anderes Fenster auf dem Desktop auswählt, hat das Zurückklicken auf Ihr Fenster keine Auswirkungen, und das Betriebssystem listet Ihre Anwendung als nicht reagierend auf. DoEvents () führt viel mehr als nur eine Aktualisierung durch, da es über das Nachrichtensystem mit dem Betriebssystem interagiert.
ThunderGr

4

Ich habe viele kommerzielle Anwendungen mit dem "DoEvents-Hack" gesehen. Besonders wenn das Rendern ins Spiel kommt, sehe ich oft Folgendes:

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

Sie alle wissen um das Böse dieser Methode. Sie verwenden den Hack jedoch, weil sie keine andere Lösung kennen. Hier sind einige Ansätze aus einem Blog-Beitrag von Tom Miller :

  • Stellen Sie Ihr Formular so ein, dass alle Zeichnungen in WmPaint ausgeführt werden, und rendern Sie dort. Stellen Sie vor dem Ende der OnPaint-Methode sicher, dass Sie this.Invalidate () ausführen. Dadurch wird die OnPaint-Methode sofort erneut ausgelöst.
  • P / Rufen Sie die Win32-API auf und rufen Sie PeekMessage / TranslateMessage / DispatchMessage auf. (Doevents macht tatsächlich etwas Ähnliches, aber Sie können dies ohne die zusätzlichen Zuweisungen tun).
  • Schreiben Sie Ihre eigene Formularklasse, die ein kleiner Wrapper um CreateWindowEx ist, und geben Sie sich die vollständige Kontrolle über die Nachrichtenschleife. - Entscheiden Sie, dass die DoEvents-Methode für Sie gut funktioniert, und bleiben Sie dabei.


3

Mit DoEvents kann der Benutzer herumklicken oder andere Ereignisse eingeben und auslösen, und Hintergrundthreads sind ein besserer Ansatz.

Es gibt jedoch immer noch Fälle, in denen Probleme auftreten können, bei denen Ereignismeldungen gelöscht werden müssen. Ich bin auf ein Problem gestoßen, bei dem das RichTextBox-Steuerelement die ScrollToCaret () -Methode ignorierte, wenn das Steuerelement Nachrichten in der Warteschlange hatte, die verarbeitet werden sollten.

Der folgende Code blockiert alle Benutzereingaben während der Ausführung von 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
Sie sollten Ereignisse immer blockieren, wenn Sie doevents aufrufen. Andernfalls werden andere Ereignisse in Ihrer App als Reaktion ausgelöst und Ihre App kann zwei Dinge gleichzeitig ausführen.
FastAl

2

Application.DoEvents kann Probleme verursachen, wenn etwas anderes als die Grafikverarbeitung in die Nachrichtenwarteschlange gestellt wird.

Dies kann nützlich sein, um Fortschrittsbalken zu aktualisieren und den Benutzer über den Fortschritt beim Erstellen und Laden von MainForm zu informieren, wenn dies eine Weile dauert.

In einer kürzlich von mir erstellten Anwendung habe ich DoEvents verwendet, um einige Beschriftungen auf einem Ladebildschirm jedes Mal zu aktualisieren, wenn ein Codeblock im Konstruktor meiner MainForm ausgeführt wird. Der UI-Thread war in diesem Fall mit dem Senden einer E-Mail auf einem SMTP-Server beschäftigt, der SendAsync () -Aufrufe nicht unterstützte. Ich hätte wahrscheinlich einen anderen Thread mit den Methoden Begin () und End () erstellen und von dort aus Send () aufrufen können, aber diese Methode ist fehleranfällig und ich würde es vorziehen, wenn die Hauptform meiner Anwendung während der Erstellung keine Ausnahmen auslöst.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.