it-swarm.com.de

Verwendung von Application.DoEvents ()

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 bei VB6 DoEvents?

245
Craig Johnston

Aus meiner Erfahrung würde ich bei der Verwendung von DoEvents in .NET sehr vorsichtig sein. Bei der Verwendung von DoEvents in einem TabControl mit DataGridViews sind einige sehr merkwürdige Ergebnisse aufgetreten. Wenn Sie dagegen nur ein kleines Formular mit einem Fortschrittsbalken haben, ist es möglicherweise in Ordnung.

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

13
Craig Johnston

Hmya, das beständige Mysterium von DoEvents (). Es gab eine enorme Menge an Gegenreaktionen, aber niemand erklärt jemals wirklich, warum es "schlecht" ist. Dieselbe Weisheit wie "Keine Struktur mutieren". Ä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 wissen gena was es tut, was im Fall von DoEvents () definitiv nicht leicht zu verstehen ist.

Auf Anhieb: Fast jedes Windows Forms-Programm enthält tatsächlich einen Aufruf von DoEvents (). Es ist geschickt verkleidet, jedoch mit einem anderen Namen: ShowDialog (). Mit DoEvents () kann ein Dialogfeld modalisiert werden, ohne dass die restlichen Fenster in der Anwendung eingefroren werden.

Die meisten Programmierer möchten DoEvents verwenden, um zu verhindern, dass die Benutzeroberfläche beim Schreiben einer eigenen modalen Schleife einfriert. Das tut es bestimmt; Es sendet Windows-Nachrichten und erhält alle Paint-Anfragen zugestellt. Das Problem ist jedoch, dass es nicht selektiv ist. Es versendet nicht nur Paint-Nachrichten, sondern liefert auch alles andere.

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

Es gibt noch mehr: Der Benutzer kann auf denselben Menüpunkt oder dieselbe Schaltfläche klicken, der bzw. die dieselbe Schleife startet. 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 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 nicht mit einer Ausnahme bombardiert, werden die Daten mit Sicherheit alle zur Hölle verschlüsselt.

Zurück zum ShowDialog (). Es führt DoEvents () aus, beachtet aber, dass es etwas anderes tut. Es deaktiviert alle Fenster in der Anwendung, außer dem Dialogfeld. Jetzt, da das 3-Fuß-Problem gelöst ist, kann der Benutzer nichts mehr tun, um die Logik durcheinander zu bringen. Sowohl der Fehler beim Schließen des Fensters als auch beim erneuten Starten des Jobs wurde 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 des Codes. Es macht Dialoge extrem nervig; Wer hasst es nicht, einen aktiven Dialog zu haben und nicht in der Lage zu sein, etwas aus einem anderen Fenster zu kopieren und einzufügen? Aber das ist der Preis.

Welche ist das, was es braucht, um DoEvents sicher in Ihrem Code zu verwenden. Das Festlegen der Enabled-Eigenschaft aller Formulare auf false ist eine schnelle und effiziente Methode, um Probleme zu vermeiden. Natürlich macht das noch kein Programmierer so gern. Und nicht. Aus diesem Grund sollten Sie DoEvents () nicht verwenden. Sie sollten Threads verwenden. Obwohl sie Ihnen ein ganzes 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 ihren schießen.

Die nächsten Versionen von C # und VB.NET werden eine andere Waffe mit den neuen Schlüsselwörtern await und async bereitstellen. Inspiriert zu einem kleinen Teil von den Problemen, die durch DoEvents und Threads verursacht wurden, aber zu einem großen Teil von WinRTs API-Design, das erfordert Sie, um Ihre Benutzeroberfläche auf dem neuesten Stand zu halten, während ein asynchroner Vorgang stattfindet. Wie das Lesen aus einer Datei.

454
Hans Passant

Es kann sein, aber es ist ein Hack.

Siehe Ist DoEvents Evil?.

Direkt von der MSDN-Seite that thedev referenziert:

Beim Aufruf dieser Methode wird das aktuelle Thread, der angehalten werden soll, während alle Wartefenster-Meldungen werden verarbeitet . Wenn eine Nachricht bewirkt, dass ein Ereignis .__ ist. ausgelöst, dann andere Bereiche Ihres Anwendungscode kann ausgeführt werden. Das kann veranlassen, dass Ihre Bewerbung unerwartete Verhaltensweisen wie schwer zu debuggen. Wenn Sie durchführen Operationen oder Berechnungen, für die ein Lange Zeit ist es oft vorzuziehen Führen Sie diese Vorgänge für ein neues .__ aus. Faden. Weitere Informationen zu asynchrone Programmierung siehe Asynchrone Programmierung - Übersicht.

So warnt Microsoft vor seiner Verwendung.

Außerdem halte ich es für einen Hack, weil sein Verhalten unvorhersehbar ist und Nebeneffekte neigen (dies kommt von der Erfahrung, dass versucht wird, DoEvents zu verwenden, anstatt einen neuen Thread hochzudrehen oder Hintergrundarbeiter zu verwenden).

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

27
RQDQ

Ja, es gibt eine statische DoEvents-Methode in der Application-Klasse im System.Windows.Forms-Namespace. System.Windows.Forms.Application.DoEvents () kann verwendet werden, um die in der Warteschlange des UI-Threads in der Warteschlange wartenden Nachrichten zu verarbeiten, wenn eine lang andauernde Aufgabe im UI-Thread ausgeführt wird. Dies hat den Vorteil, dass die Benutzeroberfläche schneller reagiert und nicht "eingesperrt" wird, während eine lange Aufgabe ausgeführt wird. Dies ist jedoch fast immer NICHT die beste Methode, um etwas zu tun. Wenn laut DoEvents von Microsoft "der aktuelle Thread angehalten wird, werden alle wartenden Fensternachrichten verarbeitet." Wenn ein Ereignis ausgelöst wird, können unerwartete und intermittierende Fehler auftreten, die nur schwer zu finden sind. Wenn Sie eine umfangreiche Aufgabe haben, ist es viel besser, dies in einem separaten Thread zu tun. Durch das Ausführen langer Aufgaben in einem separaten Thread können sie verarbeitet werden, ohne dass die Benutzeroberfläche störungsfrei läuft. Siehe here für weitere Details.

Here ist ein Beispiel für die Verwendung von DoEvents. Beachten Sie, dass Microsoft auch eine Warnung vor der Verwendung gibt.

23
Bill W

Ja.

Wenn Sie jedoch Application.DoEvents verwenden müssen, deutet dies meistens auf ein schlechtes Anwendungsdesign hin. Vielleicht möchten Sie lieber in einem separaten Thread arbeiten?

10

Ich habe viele kommerzielle Anwendungen gesehen, die den "DoEvents-Hack" verwenden. Besonders wenn Rendering ins Spiel kommt, sehe ich oft Folgendes:

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

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

  • Stellen Sie Ihr Formular so ein, dass alle Zeichnungen in WmPaint ausgeführt werden, und führen Sie das Rendern dort aus. Stellen Sie vor dem Ende der OnPaint-Methode sicher, dass Sie Folgendes tun: this.Invalidate (); Dadurch wird die OnPaint-Methode sofort erneut ausgelöst.
  • P/Invoke in die Win32-API 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 legen Sie die vollständige Kontrolle über die Nachrichtenschleife fest. - Stellen Sie fest, dass die DoEvents-Methode für Sie gut funktioniert, und bleiben Sie dabei.
4
Matthias

Schauen Sie sich die MSDN-Dokumentation für die Application.DoEvents -Methode an.

3
thedev

Ich sah jherikos Kommentar oben und stimmte zunächst zu, dass ich keinen Weg finden konnte, DoEvents zu vermeiden, wenn Sie Ihren Haupt-UI-Thread drehen würden, der darauf wartet, dass ein lang andauernder asynchroner Code auf einen anderen Thread abgeschlossen wird. Durch die Antwort von Matthias kann ein einfaches Aktualisieren eines kleinen Bereichs auf meiner Benutzeroberfläche die DoEvents ersetzen (und einen unangenehmen Nebeneffekt vermeiden).

Weitere Einzelheiten zu meinem Fall ...

Ich habe folgendes gemacht (wie vorgeschlagen hier ), um sicherzustellen, dass ein Fortschrittsbalken-Begrüßungsbildschirm ( Wie ein Overlay "Loading" angezeigt wird ... ) während eines lang laufenden SQL-Befehls aktualisiert wird:

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
}

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

Die gute Antwort: Ersetzen Sie die DoEvents-Zeile durch einen einfachen Refresh-Aufruf in einem kleinen Fenster in der Mitte meines Begrüßungsbildschirms, FormSplash.Panel1.Refresh(). Die Aktualisierung der Benutzeroberfläche war sehr gut und die DoEvents-Verrücktheit, vor der andere gewarnt hatten, war verschwunden.

3
TamW

Application.DoEvents kann zu Problemen führen, wenn etwas anderes als Grafikverarbeitung in die Nachrichtenwarteschlange gestellt wird. 

Es kann nützlich sein, um Fortschrittsbalken zu aktualisieren und den Benutzer über den Fortschritt in Bezug auf die Erstellung und das Laden von MainForm zu informieren, wenn dies eine Weile dauert.

In einer kürzlich von mir erstellten Anwendung habe ich DoEvents verwendet, um einige Labels auf einem Ladebildschirm jedes Mal zu aktualisieren, wenn ein Codeblock im Konstruktor meiner MainForm ausgeführt wird. Der UI-Thread war in diesem Fall damit beschäftigt, eine E-Mail an einen SMTP-Server zu senden, der keine SendAsync () - Aufrufe unterstützte. Ich hätte wahrscheinlich einen anderen Thread mit den Methoden Begin () und End () erstellt und Send () von ihren aufgerufen, aber diese Methode ist fehleranfällig und ich würde es vorziehen, wenn das Hauptformular meiner Anwendung keine Ausnahmen während der Konstruktion auslöst.

2
Guest123

Die DoEvents ermöglichen es dem Benutzer, andere Ereignisse anzuklicken oder zu tippen und auszulösen, und Hintergrundthreads sind ein besserer Ansatz.

Es gibt jedoch immer noch Fälle, in denen Probleme auftreten können, die das Löschen von Ereignismeldungen erfordern. Ich bin auf ein Problem gestoßen, bei dem das RichTextBox-Steuerelement die ScrollToCaret () - Methode ignorierte, wenn das Steuerelement Nachrichten in der zu verarbeitenden Warteschlange hatte.

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
    }
}
0