Laden...

Exception fangen und weiter zum GUI werfen funktioniert nicht. (Backgroundworker schuld?)

Erstellt von SvartfaR vor 13 Jahren Letzter Beitrag vor 13 Jahren 6.312 Views
S
SvartfaR Themenstarter:in
11 Beiträge seit 2007
vor 13 Jahren
Exception fangen und weiter zum GUI werfen funktioniert nicht. (Backgroundworker schuld?)

Hallo zusammen,

ich habe eine Formsanwendung die eine große Textdatei einliest (~3,7GB) und daraus 3 kleinere nach bestimmten Vorgaben erzeugt. Wegen der Dateigröße verwende ich im Forms einen Backgroundworker (vielleicht ist das wichtig). Die Logik zum lesen und erzeugen der Textdateien liegt in der Klasse Engine.
Ich möchte nun im Fehlerfall in der Klasse Engine eine Exception werfen, so dass innerhalb des Forms angemessen reagiert werden kann. Nur leider bekomme ich (im Debugger) immer die Meldung "Exception wurde nicht behandelt". Führe ich das Programm so aus bekomme ich die Meldung "Anwendung reagiert nicht mehr".

Hier mal der Button, der das ganze im Forms anstößt.

        private void buttonStart_Click(object sender, EventArgs e)
        {
            if (this.saveFileDialog1.ShowDialog() == DialogResult.OK)
            {
                this.start = DateTime.Now;

                this.buttonStart.Enabled = false;
                this.buttonCancel.Enabled = true;

                try
                {
                    engine = new Engine(this.textBoxFilePath.Text, this.saveFileDialog1.FileName, this.backgroundWorker1);

                    this.backgroundWorker1.RunWorkerAsync();
                }
                catch (Exception ex)
                {
                    this.ShowErrorMessage(ex);
                }
            }
        }

Die DoWork im Forms sieht folgendermaßen aus

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                Thread thread = new Thread(engine.Run);

                thread.Start();

                while (!thread.IsAlive);

                while (!engine.IsReady)
                {
                    Thread.Sleep(50);

                    if (this.backgroundWorker1.CancellationPending)
                    {
                        e.Cancel = true;
                        this.engine.RequestStop();
                        return;
                    }
                }
            }
            catch (Exception ex)
            {
                this.ShowErrorMessage(ex);
            }

        }

Nun zum werfen der Exception innerhalb der Engine-Klasse:

        public void Run()
        {
            StreamReader sr = new StreamReader(this.fileInputPath, System.Text.Encoding.Default);
            
            TextFile tdTrans = new TextFile(this.fileTrans);
            TextFile tdIntron = new TextFile(this.fileIntron);
            TextFile tdExon = new TextFile(this.fileExon);

            // Weitere Initialisierungen

            try
            {

                while (!sr.EndOfStream && !this._shouldStop)
                {
                    // Hier kann es zu einem Fehler kommen.
                }
            }
            catch (Exception e)
            {
                throwException = true;
                exInnerMessage = e.InnerException;
                exMessage = e.Message;
            }
            finally
            {
                tdTrans.Close();
                tdExon.Close();
                tdIntron.Close();
            }

            if (throwException)
            {
                throw new Exception(exMessage, exInnerMessage); // Hier kommt Meldung "Exception wurde nicht behandelt" - Message: startIndex darf nicht länger als die Länge der Zeichenfolge sein. Parametername: startIndex
            }
        }

Ich habe erwartet das hier die Exception weiter geworfen wird und dann im Forms behandelt werden kann. Aber leider scheint das nicht zu funktionieren, denn das Programm bleibt hier stehen.

Ich vermute das es irgendwie mit dem Backgroundworker zusammenhängen könnte.
Für Tipps und Ideen wäre ich sehr dankbar.

Gruß
SvartfaR

3.430 Beiträge seit 2007
vor 13 Jahren

Hallo SvartfaR,

das Problem ist dass das RunWorkerAsync() wie der Name schon sagt asynchron ist.
Somit springt er dir sofort aus deinem Try-Catch Block.
Deshalb werden die Exceptions nicht abgefangen.

Besser geeignet ist in diesem Fall ein Event welches du aus deinem Thread schmeisst und dann kann dein Form darauf reagieren

Ich habe gesehen dass du im Backgroundworker einen neuen Thread erstellst.
Das bringt nicht viel, da der BGWorker schon asynchron ist. Du brauchst also nicht noch einen erstellen.
Achso.. Damit hast du so einen Is-Alive checker gebaut. Naja, das könntest du auch direkt im Form machen und einfach mit einem Timer abprüfen ob da noch was läuft.

Guck dir mal das hier [FAQ] Warum blockiert mein GUI? an

Gruß
Michael

6.862 Beiträge seit 2003
vor 13 Jahren

Hallo,

dein Aufbau ist extrem wirr.

Du übergibst deiner Engine Klasse den BackgroundWorker - wieso? Dann startest du im DoWork des BW nen etxra Thread und führst da ne Methode der Engine aus - das ist vollkommen unsinnig da die DoWork doch schon die Methode in einem extra Thread ist. Dann so Konstrukte wie

while (!thread.IsAlive);

Wie wärs mit nem Thread.Join? Aber wie gesagt, der Thread ist unnötig. Guck dir lieber an wie man richtig mit dem BW umgeht. in der Doku zu dem sthet auch wie das mit Exceptions gehandelt wird.

Baka wa shinanakya naoranai.

Mein XING Profil.

S
SvartfaR Themenstarter:in
11 Beiträge seit 2007
vor 13 Jahren

Erstmal Danke für die schnellen Antworten.

Ihr habt recht, ein Blick ins MSDN hätte in der Tat nicht geschadet. ^^

Habe jetzt den BW überarbeitet und auch innerhalb der Engine-Klasse entsprechende Änderungen vorgenommen. Leider konnte das mein Problem mit der Exception nicht lösen, dies bleibt nach wie vor das gleiche. Außerdem wird die RunWorkerCompleted jetzt zweimal aufgerufen.

Ich poste mal den Code der Form und der Engine. Vielleicht seht ihr schon auf Anhieb wo der Fehler liegt, ihr seid da ja fit. 😉

Form1.cs:

    public partial class Form1 : Form
    {
        private DateTime start;
        private DateTime end;

        public Form1()
        {
            InitializeComponent();
            InitializeBackgroundWorker();
        }

        private void InitializeBackgroundWorker()
        {
            this.backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
            this.backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
            this.backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
        }

        private void buttonOpenFileDialog_Click(object sender, EventArgs e)
        {
            if (this.openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                this.textBoxFilePath.Text = this.openFileDialog1.FileName;
                this.buttonStart.Enabled = true;
            }
        }

        private void buttonStart_Click(object sender, EventArgs e)
        {
            if (this.saveFileDialog1.ShowDialog() == DialogResult.OK)
            {
                this.start = DateTime.Now;

                this.buttonStart.Enabled = false;
                this.buttonCancel.Enabled = true;

                this.backgroundWorker1.RunWorkerAsync();
            }
        }

        private void ShowErrorMessage(Exception ex)
        {
            // MessageBox + Fehlerbericht senden auf Wunsch
        }


        private void infoToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // MessageBox
        }

        private void buttonCancel_Click(object sender, EventArgs e)
        {
            if (MessageBox.Show("Wollen Sie die Bearbeitung wirklich abbrechen?", "Wirklich abbrechen?", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button3) == DialogResult.Yes)
            {
                this.backgroundWorker1.CancelAsync();
                this.buttonCancel.Enabled = false;
            }
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            Engine engine = new Engine(this.textBoxFilePath.Text, this.saveFileDialog1.FileName);
            e.Result = engine.Run(worker, e);
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.progressBar1.Value = e.ProgressPercentage;
            this.labelElements.Text = string.Format("{0:0,0}", e.ProgressPercentage);
        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                this.ShowErrorMessage(e.Error);
            }
            else if (e.Cancelled)
            {
                // MessageBox
            }
            else
            {
                // MessageBox
            }

            this.buttonStart.Enabled = true;
            this.buttonCancel.Enabled = false;
        }
    }

Engine.cs

    public class Engine
    {
        // Klassenvariablen

        public Engine(string input, string output)
        {
            // Typische Anweisungen innerhalb eines Konstrukturs
        }

        public long Run(BackgroundWorker worker, DoWorkEventArgs e)
        {
            StreamReader sr = new StreamReader(this.fileInputPath, System.Text.Encoding.Default);

            TextFile tdTrans = new TextFile(this.fileTrans);
            TextFile tdIntron = new TextFile(this.fileIntron);
            TextFile tdExon = new TextFile(this.fileExon);

            bool throwException = false;
            string exMessage = "";
            Exception exInnerMessage = new Exception();

            // weitere Deklarationen...

            try
            {
                while (!sr.EndOfStream && !worker.CancellationPending)
                {
                   // Hier kann es zum Fehler kommen.
                }
            }
            catch (Exception ex)
            {
                throwException = true;
                exInnerMessage = ex.InnerException;
                exMessage = ex.Message;

            }
            finally
            {
                tdTrans.Close();
                tdExon.Close();
                tdIntron.Close();
            }

            if (worker.CancellationPending)
            {
                e.Cancel = true;
            }

            if (throwException)
            {
                throw new Exception(exMessage, exInnerMessage);
            }

            return this.elements;
        }
    }
5.299 Beiträge seit 2008
vor 13 Jahren

Hi!

Ich glaub, du mußt dich um die Exception nicht kümmern. Also fange die nicht, dann fängt der BW die für dich, und speichert sie gleich in e.Error.

Bei CancelationPending solltest du die Methode umgehend beenden, sonst nichts. e.Cancel trägt der BW schon selber ein.

Du darfst dich also glaub auf das konzentrieren, was die Methode tun soll:


    public long Run(BackgroundWorker worker)     {
      using(var sr = new StreamReader(this.fileInputPath, System.Text.Encoding.Default))
      using(var tdTrans = new TextFile(this.fileTrans))
      using(var tdIntron = new TextFile(this.fileIntron))
      using(var tdExon = new TextFile(this.fileExon)){
         while (!sr.EndOfStream && !worker.CancellationPending)         {
            // Hier kann es zum Fehler kommen.
         }
      }
      return this.elements;
    }

Der frühe Apfel fängt den Wurm.

S
SvartfaR Themenstarter:in
11 Beiträge seit 2007
vor 13 Jahren

Leider klappt das nicht, das Programm bleibt dann beim Fehler stehen.
Habe mich dabei auch an diesem MSDN-Beitrag orientiert: BackgroundWorker-Klasse
Auch hier wird explizit eine Exception geworfen und e.Cancel auf true gesetzt.

Nebenbei: An using habe ich gar nicht gedacht, gefällt mir aber gut die Lösung.

3.430 Beiträge seit 2007
vor 13 Jahren

Hallo,

Leider klappt das nicht, das Programm bleibt dann beim Fehler stehen.

Wie es bleibt stehen? Bzw. was für einen Fehler bekommst du und wo bekommst du diesen.

Das Programm läuft nie normal weiter wenn es eine Exception hagelt.
Es springt bis zum nächsten Punkt wo es abgefangen wird (try catch) und darin musst du den Fehler behandeln und sicherstellen dass das Programm danach wieder weiterlaufen kann ohne dass es durch die entstandene Exception zu vielen Folgefehlern kommt.

Gruss
Michael

I
1.739 Beiträge seit 2005
vor 13 Jahren

Schon mal drüber nachgedacht, das 3,7 GB zu viel sind um sie auf einmal einzulesen?
Ich frag ja nur.

S
SvartfaR Themenstarter:in
11 Beiträge seit 2007
vor 13 Jahren

Leider klappt das nicht, das Programm bleibt dann beim Fehler stehen.

Wie es bleibt stehen? Bzw. was für einen Fehler bekommst du und wo bekommst du diesen

Ohne try-catch bspw. in einem Objekt innerhalb der while-Schleife stehen. Zum Beispiel wie im angehängten Bild. Mit try-catch kam bisher immer die Meldung "Unbehandelte Ausnahme"/"Nicht behandelte Ausnahme" genau an der Stelle wo man sie innerhalb der Run() wirft. Jetzt auf einmal zeigt sich hier ein anderes Verhalten. Das Programm beendet einfach an der Stelle. Es geht nicht mal mehr in die RunWorkerCompleted().

Schon mal drüber nachgedacht, das 3,7 GB zu viel sind um sie auf einmal einzulesen?
Ich frag ja nur.

Die gesamten 3,7GB lese ich nicht auf einmal ein. Es handelt sich dabei um biologische Daten die leider in so riesigen Textdateien gespeichert sind. Darin sind DNA-Sequenzen die ich nach bestimmten Angaben (ebenfalls in dieser Datei) auslesen muss und in 3 Varianten speichern muss. Ich lese also immer eine komplette Sequenz die dann verarbeitet wird und danach die Nächste.

5.299 Beiträge seit 2008
vor 13 Jahren

Leider klappt das nicht, das Programm bleibt dann beim Fehler stehen.

Ja, das ist doch das bestmögliche Verhalten!
Das gibt dir die Möglichkeit, den Fehler zu beheben.

Programmiere so, dass der StartIndex niemals länger wird als die Zeichenfolge - das ist sicher möglich.

wieder mal ein Beispiel, wie man sich mit TryCatch das Debuggen erschwert/verunmöglicht. gugge auch AvoidTryCatch

Der frühe Apfel fängt den Wurm.

S
SvartfaR Themenstarter:in
11 Beiträge seit 2007
vor 13 Jahren

Dieser Fehler sollte genau genommen auch nicht vorkommen, es muss sich hier eine falsche Angabe in der zu lesenden Datei handeln. Das war jetzt Zufall das ich diesen Fall auch bei der Entwicklung habe. Allerdings gibt es von diesen 3,7GB Dateien nicht nur eine und ich selber werde die auch später nicht einlesen. Ich schreibe das Tool für einen Freund der seinen Doktor in Biologie macht und der mit den extrahierten Daten weiterarbeitet. Wenn die das Tool im Labor benutzen ist es mir lieber es sagt einem das es beim Xten Datensatz folgendes Problem gab anstatt hart abzustürzen.

Ich kann unmöglich jeden möglichen Fehler Abfragen, der beim Einlesen entstehen könnte. Zum entwickeln gebe ich dir 100% Recht, da sind try-catch bei mir auch auskommentiert.

from und to ergeben sich beispielsweise aus einer Angabe wie 3300100..3300200. Das sind Positionen innerhalb einer Sequenz die ich auslesen muss. Ist diese Angabe in der zu lesenden Datei falsch (warum auch immer) muss ich das Programm sowieso beenden, denn die Reihenfolge ist ebenfalls wichtig. Ich könnte natürlich abfragen ob hier entsprechende Fehler auftreten wird und darauf eine spezielle Meldung ausgeben können. Aber spontan hätte ich jetzt wohl eine spezifische Exception geschmissen mit einer Meldung mit der Biologen auch was anfangen können. Und das ist der Punkt wo wir wieder am Anfang wären.

849 Beiträge seit 2006
vor 13 Jahren

Hallo,

das Programm bleibt nicht stehen. Das Studio springt nur zu der Exception, weil es mitbekommen hat, das da was schief läuft. Wenn Du das Programm ohne Debugger startest wird es auch nicht "stehen bleiben". Einfach F5 drücken, und das Programm schmeisst die Exception weiter nach oben.

Gruß

S
SvartfaR Themenstarter:in
11 Beiträge seit 2007
vor 13 Jahren

das Programm bleibt nicht stehen. Das Studio springt nur zu der Exception, weil es mitbekommen hat, das da was schief läuft. Wenn Du das Programm ohne Debugger startest wird es auch nicht "stehen bleiben". Einfach F5 drücken, und das Programm schmeisst die Exception weiter nach oben.

Ja, habe es gerade auch noch einmal in der MSDN-Library nachgelesen:

If the operation raises an exception that your code does not handle, the BackgroundWorker catches the exception and passes it into the RunWorkerCompleted event handler, where it is exposed as the Error property of System.ComponentModel.RunWorkerCompletedEventArgs. If you are running under the Visual Studio debugger, the debugger will break at the point in the DoWork event handler where the unhandled exception was raised. If you have more than one BackgroundWorker, you should not reference any of them directly, as this would couple your DoWork event handler to a specific instance of BackgroundWorker. Instead, you should access your BackgroundWorker by casting the sender parameter in your DoWork event handler.

Werde das heute Abend noch einmal alles genau durchtesten.

S
SvartfaR Themenstarter:in
11 Beiträge seit 2007
vor 13 Jahren

Starte ich das Programm direkt über die Exe und nicht über VS so erhalte ich angehängten Meldungen. Das kann ja auch nicht richtig sein.

Führe ich es über VS aus bleibt es beim Fehler bzw. beim werfen einer expliziten Exception stehen. Nach einem Druck auf F5 beendet das Programm einfach. Es geht niemals in die RunWorkerCompleted().

3.430 Beiträge seit 2007
vor 13 Jahren

Hallo SvartfaR,

dieses Verhalten ist ganz normal.
Wo soll er dir schon hinspringen.

Im VS reagiert dir das Visual Studio auf den Fehler und springt zur entsprechenden Zeile um diesen anzuzeigen.
Der Fehler wird aber nur angezeigt? Das Programm ist schon abgestürtzt!

Wenn du es normal ohne VS startest dann reagiert es identisch (nur dass er die die Codezeile nicht anzeigt).

Du musst den Fehler in deinem Code mit einem Try-Catch Block abfangen und darin auf den Fehler reagieren

Bitte beachte: [Hinweis] Wie poste ich richtig? 1.1 und 1.1.1

Gruss
Michael

5.299 Beiträge seit 2008
vor 13 Jahren

Ich habe ihn so verstanden, dass er einen Fehler werfen will, und den dann im MainThread, in BGW.RunWorkerCompleted auswerten möchte, nämlich eine Ausgabe machen, dass die eingelesene Datei korrupte Daten enthält.

Zur Laufzeit scheint das zu funktionieren, zur Designzeit nicht.

Ich kenne mich mit Fehlern im Nebenthread nicht sonderlich aus, u.v.a. nicht, wie der BGW damit umgeht.

Wenn du den Fehler fängst, muß das im selben Thread des Fehlers geschehen.
Evtl. musst du ein spezielles Result proggen, welches angibt: Fehler oder Resultat.

Und das vorgesehene Feature des BGWs halt als unbrauchbar verwerfen (ich find das jdfs. recht komisch, dass man das BGW-Verhalten nicht debuggen kann, weil der Debugger beim Fehler stoppt).

Eine Möglichkeit ist noch, die Anwendung als Release-Kompilation zu debuggen, aber wenn das geht, isses trotzdem bischen Murks, weil die anderen Komponenten will man vmtl. als Debug-Kompilation debuggen.

Der frühe Apfel fängt den Wurm.

S
SvartfaR Themenstarter:in
11 Beiträge seit 2007
vor 13 Jahren

> ](http://www.mycsharp.de/wbb2/thread.php?threadid=26594) 1.1 und 1.1.1){gray}

Hallo Michael,

entschuldige bitte falls es den Eindruck gemacht habe, ich würde nicht selbst genug recherchieren oder gar ein ein Anfänger sein. Ich habe stets versucht möglichst genau zu antworten damit alles nachvollziehbar ist.
Das ich in der Entwicklung von Forms-Anwendungen kein Experte bin, das dürft ihr mir gerne vorwerfen und das ich mich beim BW zu Beginn mal eher an ein brauchbares Beispiel hätte orientieren soll auch. 😉 Komme eher aus der ASP.NET und Java-Welt.
Werde ich das nächste mal besser drauf achten.

Ich habe die Ursache jetzt endlich gefunden, warum bei mir die RunWorkerCompleted() nicht mehr aufgerufen wurde und das Programm immer mit einem Absturz endete.
Als ich meine finally-Klausel in der Run() entfernt habe, habe ich in der Klasse TextFile IDisposable implementiert und in der Methode Dispose() (warum auch immer 😭) diese nochmals aufgerufen anstatt nur den Stream zu schließen.

Jetzt wird die RunWorkerCompleted() aufgerufen, auch im Fehlerfall.

Einfach so auf den BW versteift und nicht gemerkt das er direkt nicht schuld ist. 😭

Die RunWorkerCompleted() wird momentan noch zweimal aufgerufen, aber das eigentliche Problem ist ja gelöst. Vielen Dank für alle helfenden Stimmen und ja, ihr dürft jetzt mit den Augen rollen. =)