2009-03-17 9 views
2

Ich habe einen merkwürdigen Unterschied zwischen einem Programm, das in VS2005 ausgeführt wird, und dem Ausführen der ausführbaren Datei direkt festgestellt. Wenn eine Ausnahme in einer Methode innerhalb eines Aufrufs Application.DoEvents() ausgelöst wird, kann die Ausnahme bei dem Ausführen in Visual Studio gefangen werden. Wenn die kompilierte ausführbare Datei ausgeführt wird, wird die Ausnahme nicht abgefangen und das Programm stürzt ab.Können Sie Ausnahme innerhalb von Application.DoEvents() abfangen?

Hier ist ein einfacher Code, um das Problem zu demonstrieren. Angenommen, Standard Winforms Boilerplate und zwei Tasten und ein Label.

Um dies auszuführen, klicken Sie auf die Schaltfläche Start, um die Zählung von 10 Sekunden zu starten. Bevor 10 Sekunden verstreichen, drücken Sie die Abbruch-Taste. und eine Ausnahme wird innerhalb der DoEvents() geworfen werden. Die Ausnahme sollte abgefangen werden. Dies geschieht nur, wenn Sie in Visual Studio ausgeführt werden.

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 zu fangen. Gibt es eine Möglichkeit, es zum Laufen zu bringen?

Update:

Ich bin bereit, Ansätze zu betrachten andere als die einspringenden-Kopfschmerzen auslösende DoEvents(). Aber ich habe keinen gefunden, der besser zu funktionieren scheint. Mein Szenario ist, dass ich eine lange laufende Schleife habe, die einige wissenschaftliche Instrumente steuert, und häufig auf eine Temperatur warten muss, um sich zu stabilisieren oder so etwas. Ich möchte meinen Benutzern die Möglichkeit geben, den Prozess abzubrechen. Daher habe ich eine Schaltfläche zum Abbrechen, die einfach eine benutzerdefinierte Ausnahme auslöst, die ich an der Stelle abfangen möchte, an der der Prozess ursprünglich gestartet wurde. Es schien eine perfekte Lösung zu sein. Bis auf die Tatsache, dass es aus irgendeinem Grund nicht funktioniert.

Wenn es nicht möglich ist, dies zur Arbeit zu bringen, gibt es einen besseren Ansatz?

Update 2:

Als ich dies als die erste Zeile der Main() hinzufügen, dass es funktionieren als eine ausführbare, aber nicht in VS, so ist die Situation umgekehrt ist, macht. Das Verrückte ist, dass es ein No-Op zu sein scheint. Ich kann verstehen, wie das alles macht.

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

Das ist verrückt.

Antwort

6

Haben Sie wirklich habenDoEvents zu verwenden? Es führt zu Re-Enrancy, die sehr schwer zu debuggen sein kann.

Ich vermute, dass Sie, wenn Sie die Re-entrance aus Ihrer Anwendung entfernen, herausfinden können, wo Sie die Ausnahme leichter abfangen können.

EDIT: Ja, es gibt definitiv eine bessere Möglichkeit, dies zu tun. Führen Sie Ihre lang andauernde Aufgabe in einem anderen Thread aus. Der UI-Thread sollte nur UI-Operationen ausführen. Setzen Sie Ihren "Abbrechen" -Button, um ein Flag zu setzen, das die Langzeitaufgabe regelmäßig überprüft. Ein Beispiel für das WinForms-Threading finden Sie unter WinForms threading page und das volatility page für ein Beispiel für einen Thread, der ein Flag setzt, das von einem anderen Thread überwacht wird.

EDIT: Ich habe gerade daran erinnere, dass BackgroundWorker (was mein Artikel bezieht sich nicht auf - es geschrieben wurde pre-.NET 2.0) eine CancelAsync Methode - im Grunde dieses (mit den verwendeten CancellationPending und WorkerSupportsCancellation der im Grunde mit den Angeboten msgstr "Habe eine Flagge, um den Vorgang abzubrechen".Überprüfen Sie einfach CancellationPending aus dem Worker-Thread, und rufen Sie CancelAsync von Ihrem Abort Button Click-Handler.

+0

Ich tat es absichtlich auf diese Weise, in der Hoffnung, meinen Benutzern einen Abbruchknopf zur Verfügung zu stellen, der einen lang andauernden Prozess tötet, der die meiste Zeit damit verbringt, auf externe Instrumente zu warten. Was ist der beste Weg, dies ohne DoEvents zu tun? (siehe auch, neues Update in OP) – recursive

+0

Verdammt, Skeet. Ich habe sorgfältig meine winzige Antwort bearbeitet, während du das redest. –

+0

Ich habe immer gehört, dass Sie immer Threads vermeiden sollten, wenn Sie können, weil das Debuggen so schwierig ist. Der Wortlaut Ihrer Änderung deutet darauf hin, dass alle Apps, die nicht über die Benutzeroberfläche verfügen (alle Apps), Multi-Threading sein sollten. Ich bin verwirrt. – recursive

2

Da DoEvents die zugrunde liegende Nachrichtenschleife zum Pumpen veranlasst, kann dies zu einem erneuten Eindringen (wie stated in MSDN) führen, wodurch das Verhalten Ihrer Anwendung unvorhersehbar wird.

Ich würde vorschlagen, dass Sie Ihre Arbeit (warten auf eine Temperatur zu stabilisieren) in einen Thread und verwenden Sie ein Signal einer Art, um Ihre Benutzeroberfläche zu melden, wenn das Warten vorbei ist. Dadurch kann Ihre Benutzeroberfläche reaktionsschnell bleiben, ohne dass Sie Application.DoEvents verwenden müssen.

1

Ich denke, Sie können möglicherweise die Ausnahme mit dem Application.ThreadException Ereignis abfangen.

Um es zuerst zu tun, müssen Sie Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); gleich zu Beginn der Anwendung festlegen, bevor alle Steuerelemente erstellt werden. Fügen Sie dann den Ereignishandler für das Ereignis ThreadException hinzu.

Wenn dies nicht funktioniert, würde ich empfehlen, eine BackgroundWorker zu verwenden, was bedeutet, dass Sie DoEvents nicht verwenden müssen, und es wäre auch eine bessere Lösung.

EDIT: In Ihrem Beispiel des ThreadException-Handlers werfen Sie eine weitere Ausnahme. Die Idee war, dass Sie den Code für die Ausnahmebehandlung einfügen und keine weitere Ausnahme auslösen.

Allerdings würde ich Sie immer noch dringend bitten, einen BackgroundWorker zu verwenden, um Ihren Loop-Code auszuführen.

1

Nicht versuchen, alte Nachrichten zu bringen, sondern weil ich mit dem gleichen Problem gekämpft habe und keine Lösung gefunden habe. Wenn Sie einen Hintergrund-Worker ausführen und Ihre Benutzer wieder im Hauptanwendungsthread arbeiten und andere Aufgaben ausführen und dieselbe Funktion ausführen möchten, die im Hintergrund-Worker liegt, scheint Application.DoEvents() der beste Weg zu sein und Sie können trotzdem benutze es. Das Problem liegt in der Tatsache, dass Sie Code in dem RunWorkerCompleted-Ereignis haben. Wenn Sie den Worker mit CancelAsync abbrechen, wird alles, was in RunWorkerCompleted vorhanden ist, ausgeführt. Das Problem ist, dass normalerweise in RunWorkerCompleted der Zugriff auf die GUI erfolgt und Sie nicht auf die GUI zugreifen können, während Sie Application.DoEvents() ausführen, Ihren Code aus RunWorkerCompmleted entfernen und ihn in die ReportProgress-Funktion integrieren. Sie müssen jedoch check-Ereignisse direkt vor der Reportprogress-Funktion auslösen, um sicherzustellen, dass der Haupt-Thread keinen weiteren Lauf anfordert. So etwas wie dieses Recht vor Report:

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

So Sie Hintergrund-Thread alle seine Arbeit tut und dann nach rechts, bevor Sie es haben Report diese Prüfung in dort zu werfen. Auf diese Weise, wenn Ihr Benutzer gesagt hat hey Ich möchte wirklich diese Abfrage auf ein anderes Element laufen sie in einer Schleife mit so etwas wie dieses warten würde:

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

Zwar ist dies offensichtlich eine schlechte Praxis, es zu benutzen vollbringt Loswerden der Ausnahme. Ich habe alles danach gesucht, bis ich anfing, ein Projekt zu modifizieren, an dem ich gerade arbeitete, und sah, dass der einzige Grund für einen Fehler darin lag, dass ich gleichzeitig auf den Haupt-Thread zugreifte, glaube ich. Wahrscheinlich wird dafür geflammt, aber es gibt eine Antwort.

Verwandte Themen