2 Stimmen

Können Sie Ausnahmen innerhalb von Application.DoEvents() abfangen?

Ich habe einen seltsamen Unterschied zwischen einem Programm, das in VS2005 ausgeführt wird, und dem direkten Ausführen der ausführbaren Datei festgestellt. Im Wesentlichen, wenn eine Ausnahme in einer Methode innerhalb einer Methode ausgelöst wird Application.DoEvents() Aufruf kann die Ausnahme bei der Ausführung in Visual Studio abgefangen werden. Wenn die kompilierte ausführbare Datei ausgeführt wird, wird die Ausnahme nicht abgefangen und das Programm stürzt ab.

Hier ist ein einfacher Code, der das Problem veranschaulicht. Gehen Sie von einer Standard-Winforms-Vorlage und zwei Schaltflächen und einem Label aus.

Klicken Sie dazu auf die Schaltfläche Start, um den 10-Sekunden-Zähler zu starten. Drücken Sie vor Ablauf der 10 Sekunden auf die Schaltfläche "Abbrechen", und es wird eine Ausnahme in der DoEvents() . Die Ausnahme sollte abgefangen werden. Dies geschieht nur bei der Ausführung in Visual Studio.

    private void StartButton_Click(object sender, EventArgs e) {
        DateTime start = DateTime.Now;

        try {
            while (DateTime.Now - start < new TimeSpan(0, 0, 10)) {
                this.StatusLabel.Text = DateTime.Now.ToLongTimeString();
                Application.DoEvents();
            }

            MessageBox.Show("Completed with no interuption.");
        } catch (Exception) {
            MessageBox.Show("User aborted.");                
        }
    }

    private void ButtonAbort_Click(object sender, EventArgs e) {
        throw new Exception("aborted");
    }

Ich möchte in der Lage sein, diese Ausnahmen abzufangen. Gibt es eine Möglichkeit, dies zu erreichen?

Aktualisierung:

Ich bin bereit, andere Ansätze als die Kopfschmerzen verursachende Wiederholung in Betracht zu ziehen DoEvents() . Aber ich habe keine gefunden, die besser zu funktionieren scheint. Mein Szenario ist, dass ich eine lange laufende Schleife habe, die einige wissenschaftliche Instrumente steuert und häufig warten muss, bis sich eine Temperatur stabilisiert oder so. Ich möchte meinen Benutzern die Möglichkeit geben, den Prozess abzubrechen, also habe ich eine Abbruchschaltfläche, die einfach eine benutzerdefinierte Ausnahme auslöst, die ich an der Stelle abfangen möchte, an der der Prozess ursprünglich gestartet wurde. Das schien eine perfekte Lösung zu sein. Bis auf die Tatsache, dass es aus irgendeinem Grund nicht funktioniert.

Wenn dies nicht möglich ist, gibt es dann einen besseren Ansatz?

Update 2:

Wenn ich dies als erste Zeile von Main() hinzufüge, funktioniert es als ausführbare Datei, aber nicht in VS, so dass die Situation umgekehrt ist. Das Verrückte daran ist, dass es ein No-op zu sein scheint. Ich kann verstehen, wie dies etwas tut.

Application.ThreadException += delegate(
        object sender, 
        System.Threading.ThreadExceptionEventArgs e
    ) 
    { throw e.Exception; };

Das ist Wahnsinn.

6voto

Jon Skeet Punkte 1325502

Wollen Sie wirklich haben を使用する。 DoEvents ? Es führt zu Reentrancy, die sehr schwer zu debuggen sein kann.

Ich vermute, dass Sie, wenn Sie die Wiederverknüpfung aus Ihrer Anwendung entfernen, leichter herausfinden können, wo die Ausnahme zu fangen ist.

EDIT: Ja, es gibt definitiv einen besseren Weg, dies zu tun. Führen Sie Ihre lang laufende Aufgabe in einem anderen Thread aus. Der UI-Thread sollte nur UI-Operationen durchführen. Lassen Sie Ihre "Abbruch"-Schaltfläche ein Flag setzen, das von der lang laufenden Aufgabe regelmäßig überprüft wird. Siehe mein WinForms Threading Seite für ein Beispiel für WinForms-Threading, und die Volatilitätsseite für ein Beispiel, bei dem ein Thread ein Flag setzt, das von einem anderen Thread beobachtet werden soll.

EDIT: Mir ist gerade eingefallen, dass BackgroundWorker (das in meinem Artikel nicht behandelt wird - es wurde vor .NET 2.0 geschrieben) hat eine CancelAsync Methode - im Grunde genommen ist dies (in Verbindung mit der CancellationPending y WorkerSupportsCancellation die sich im Grunde genommen mit dem "Haben Sie eine Flagge zu setzen, um zu annullieren" Zeug für Sie. Einfach prüfen CancellationPending vom Arbeits-Thread aus und rufen CancelAsync aus dem Handler für den Klick auf die Schaltfläche Abbrechen.

2voto

Jeff Yates Punkte 59874

Denn DoEvents die zugrundeliegende Nachrichtenschleife zum Pumpen bringt, kann dies zu Reentrancy führen (wie in MSDN angegeben ), was das Verhalten Ihrer Anwendung unvorhersehbar macht.

Ich würde vorschlagen, dass Sie Ihre Arbeit (das Warten auf die Stabilisierung einer Temperatur) in einen Thread verlagern und ein Signal verwenden, um der Benutzeroberfläche mitzuteilen, wann die Wartezeit beendet ist. Dadurch kann Ihre Benutzeroberfläche reaktionsfähig bleiben, ohne dass Sie die Anwendung.DoEvents .

1voto

Jon Mitchell Punkte 3229

Ich denke, Sie können die Ausnahme mit der Methode Application.ThreadException Veranstaltung.

Dazu müssen Sie zunächst folgende Einstellungen vornehmen Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); gleich zu Beginn der Anwendung, bevor irgendwelche Steuerelemente erstellt werden. Fügen Sie dann den Ereignishandler für die ThreadException Veranstaltung.

Wenn dies nicht funktioniert, empfehle ich die Verwendung einer BackgroundWorker was bedeutet, dass Sie keine DoEvents und es wäre auch eine bessere Lösung.

EDIT: In Ihrem Beispiel des ThreadException-Handlers lösen Sie eine weitere Ausnahme aus. Die Idee war, dass Sie Ihre Ausnahmebehandlung Code in dort setzen, nicht eine weitere Ausnahme auslösen.

Ich würde Ihnen aber trotzdem raten, einen BackgroundWorker zu verwenden, um Ihren Schleifencode auszuführen.

1voto

heavylifting Punkte 11

Ich versuche nicht, alte Nachrichten aufzugreifen, sondern weil ich mit dem gleichen Problem zu kämpfen hatte und keine Lösung gefunden habe. Wenn Sie einen Hintergrund-Worker ausführen und Ihre Benutzer sind zurück in der Hauptanwendung Thread arbeiten und andere Dinge tun und sie wollen, dass die gleiche Funktion, die auf dem Hintergrund-Worker liegt, Application.DoEvents() scheint wie der beste Weg, und Sie können es immer noch verwenden. Das Problem liegt in der Tatsache, dass Sie Code in der RunWorkerCompleted Ereignis haben. Wenn Sie den Worker selbst mit CancelAsync abbrechen, wird alles, was sich in RunWorkerCompleted befindet, ausgeführt. Das Problem ist, dass in der Regel, was auch immer in Ihrem RunWorkerCompleted ist Zugriff auf die GUI und Sie können nicht auf die GUI zugreifen, während Application.DoEvents(), entfernen Sie Ihren Code aus RunWorkerCompmleted und integrieren es in die ReportProgress-Funktion ist mehr akzeptabel. Sie müssen jedoch direkt vor der ReportProgress-Funktion Check-Ereignisse auslösen, um sicherzustellen, dass der Haupt-Thread keine weitere Ausführung anfordert. Also etwas wie dieses direkt vor reportprogress:

if (backgroundWorker1.CancellationPending == true){
     e.Cancel = true;
     return;
}
backgroundWorker1.ReportProgress(0);

Ihr Hintergrund-Thread macht also seine ganze Arbeit und dann, kurz bevor Sie es ReportProgress haben, werfen Sie diese Prüfung hinein. Auf diese Weise, wenn Ihr Benutzer gesagt hat, hey ich eigentlich diese Abfrage auf ein anderes Element ausführen möchten sie in einer Schleife mit etwas wie diesem warten würde:

if (backgroundWorker1.IsBusy == true){
     backgroundWorker1.CancelAsync();
     While(backgroundWorker1.CancellationPending == true){
          Application.DoEvents();
     }
}

Obwohl dies offensichtlich eine schlechte Praxis ist, wird die Ausnahme dadurch beseitigt. Ich habe überall danach gesucht, bis ich anfing, ein Projekt zu ändern, an dem ich arbeitete, und sah, dass der einzige Grund dafür, dass es zu Fehlern kam, darin lag, dass es gleichzeitig auf den Hauptthread zugriff, GLAUBE ich. Wahrscheinlich wird für diese flamed bekommen, aber es gibt eine Antwort.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X