Exceptions aus verschiedenen Threads



  • Hallo,

    ich hab ein Problem. Und zwar benutze ich die SerialPort Klasse und deren DataReceived Event. Diese habe ich mir in einer eigenen Klasse gekapselt um einfacher damit arbeiten zu können.

    Problem ist jetzt, dass das DataReceived Event ja in einem eigenen Thread aufgerufen wird. Wenn in dieser Methode also eine Exception geworfen wird, kann ich diese nicht effektiv fangen:

    public class SerialPortWrapper
    {
        private string data;
    
        public string Write(string commandString)
        {
            data = "";
            SerialPort serialPort = new SerialPort();
            // Initialisierung ...
    
            serialPort.Write(commandString);
            // Warte bis Daten empfangen wurden ...
    
            return data;
        }
    
        private void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            data += ((SerialPort)sender).ReadExisting(); // Sammle Daten
            throw new Exception("Something went wrong!");
        }
    }
    
    public class Test
    {
        public static void Main()
        {
            SerialPortWrapper port = new SerialPortWrapper();
    
            try
            {
                string data = port.Write("irgendwas");
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString()); // Wird nicht gefangen
            }
        }
    }
    

    Klar könnte ich den try-catch Block einfach in den DataReceived callback einbauen und dort die Fehlermeldung ausgeben; allerdings will ich es gerne dem Benutzer überlassen, wie er mit der Exception umgeht. Eine Alternative die mir noch eingefallen ist wäre, einen "ErrorHappend" event anzulegen, auf den sich der Benutzer bei Bedarf registrieren kann. Wäre aber ziemlich nervig dann an jeder Stelle wo eine Instanz der Wrapperklasse erstellt wird noch zusätzlich einen Callback zu registrieren der die selbe Fehlerbehandlung durchführt wie die restlichen try-catch Blöcke im Program.

    Wie kann ich das Problem möglichst elegant lösen?



  • Dein Beispiel is meiner Meinung nach noch nicht komplett, da noch die Methode fehlt, die die Daten tatsächlich verarbeitet. Deine serialPort_DataReceived dient ja nur dazu, die Daten entgegenzunehmen. Es wird aber ja sicherlich noch ein Event geben, für das man sich bei deiner SerialPortWrapper-Klasse anmelden kann (auch ein DataReceived oder so). In diesem Handler kannst du dann in den EventArgs eine Property "Error" einbinden, die, falls vorhanden, die Exception enthält, oder andernfalls null.

    Microsofts BackgroundWorker macht das auch so. Die schreiben dazu:

    MSDN schrieb:

    Your RunWorkerCompleted event handler should always check the AsyncCompletedEventArgs.Error and AsyncCompletedEventArgs.Cancelled properties before accessing the RunWorkerCompletedEventArgs.Result property. If an exception was raised or if the operation was canceled, accessing the RunWorkerCompletedEventArgs.Result property raises an exception.

    Oder du arbeitest ohne Threads und überlässt es dem Benutzer, sich einen Thread zu erstellen und mit deinem Wrapper zu arbeiten. Da könnte er das Ganze einfach in ein Try/Catch einbinden. Aber das willst du wahrscheinlich nicht, da du ja in deinem Beispiel schon Threads verwendest.



  • Hallo,

    danke schonmal für die Antwort.

    KlabautermannALTER schrieb:

    Dein Beispiel is meiner Meinung nach noch nicht komplett, da noch die Methode fehlt, die die Daten tatsächlich verarbeitet. Deine serialPort_DataReceived dient ja nur dazu, die Daten entgegenzunehmen. Es wird aber ja sicherlich noch ein Event geben, für das man sich bei deiner SerialPortWrapper-Klasse anmelden kann (auch ein DataReceived oder so). In diesem Handler kannst du dann in den EventArgs eine Property "Error" einbinden, die, falls vorhanden, die Exception enthält, oder andernfalls null.

    Also es gibt keinen eigenen Event für das DataReceived, die Write-Funtkion gibt einfach die empfangenen Daten als string zurück (das fehlt in dem Beispiel, das ist richtig, hab das Beispiel entsprechend angepasst). Aber letztendlich sammelt die Write Funktion nur die Daten und gibt diese dann an den Benutzer zurück, deswegen wird das so nicht gehen...

    KlabautermannALTER schrieb:

    Oder du arbeitest ohne Threads und überlässt es dem Benutzer, sich einen Thread zu erstellen und mit deinem Wrapper zu arbeiten. Da könnte er das Ganze einfach in ein Try/Catch einbinden. Aber das willst du wahrscheinlich nicht, da du ja in deinem Beispiel schon Threads verwendest.

    Also ohne Threads geht ja gar nicht (selbst wenn ich wollte), weil der DataReceived Event der SerialPort Klasse ja immer in einem eigenen Thread aufgerufen wird (dagegen kann man ja gar nichts machen). In meinem Beispiel verwende ich ja schon eigentlich gar keine Threads (außer eben den besagten DataReceived-Event, um den man aber nicht herumkommt).



  • Wenn die Write-Funktion sowieso wartet, bis Daten empfangen wurden, kannst Du dort auch die Exception schmeissen.

    Dazu fängst Du die Exception in DataReceived, speicherst sie in einem Feld der Klasse und wirfst sie dann, sofern vorhanden, in Write.



  • Achso, ich wusste nicht, dass die Write-Funktion blockiert.

    Dann stimme ich LordJaxom zu. So würde ich es dann auch machen.

    Threadsicherheit hast du dadurch natürlich keine für deine Wrapper-Klasse.



  • Hallo,

    LordJaxom schrieb:

    Wenn die Write-Funktion sowieso wartet, bis Daten empfangen wurden, kannst Du dort auch die Exception schmeissen.

    Dazu fängst Du die Exception in DataReceived, speicherst sie in einem Feld der Klasse und wirfst sie dann, sofern vorhanden, in Write.

    DessenDererWelcher schrieb:

    Achso, ich wusste nicht, dass die Write-Funktion blockiert.

    Dann stimme ich LordJaxom zu. So würde ich es dann auch machen.

    Threadsicherheit hast du dadurch natürlich keine für deine Wrapper-Klasse.

    das Problem ist dass der DataReceived Event ja auch nach der Write Funktion noch aufgerufen werden kann. Folgendes Szenario:

    SerialPortWrapper port = new SerialPortWrapper();
    string s1 = port.Write("ein command");
    string s2 = port.Write("noch ein command");
    
    Console.WriteLine(s1 + "\n" + s2);
    
    // Hier wird wider Erwartens nochmal der DataReceived-Event aufgerufen
    
    port.Close();
    

    Das liegt daran das Write noch einen string als "Ende-Zeichen" entgegen nimmt, welches signalisiert dass die Daten "fertig" sind (dachte das ist für das grundlegende Problem nicht relevant). Das sieht so aus:

    public class SerialPortWrapper
    {
        private string data;
        private bool sending = false; 
    
        public string Write(string commandString)
        {
            return Write(commandString, "\n"); // Default Ende-Zeichen ist Newline
        }
    
        public string Write(string commandString, string endSymbol)
        {
            sending = true;
            data = "";
            SerialPort serialPort = new SerialPort();
            // Initialisierung ...
    
            serialPort.Write(commandString);
            // Warte bis Daten das Ende-Zeichen "endSymbol" enthalten
    
            sending = false;
            return data;
        }
    
        private void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            if (!sending)
            {
                throw new Exception("Invalid data received!");
            }
            data += ((SerialPort)sender).ReadExisting(); // Sammle Daten
        }
    }
    

    Was jetzt letzendlich das Problem ist: Angenommen der Benutzer verschickt ein command mit einem "falschen" Ende-Zeichen. Dann denkt die Write Funktion die Daten sind schon fertig gelesen, obwohl sie das noch nicht sind (also der DataReceived Event kann weiterhin abgefeuert werden).

    Das will ich jetzt abfangen indem ich den DataReceived Event nur zulasse solange die Write Funktion aktiv ist (dafür sorgt das bool-Flag "sending"). Falls der Event außerhalb der Write Funktion aufgerufen wird, weiß ich dass der Benutzer ein nicht zueinander passendes commandString-endSymbol Paar benutzt hat und werfe eine Exception.



  • Du hast doch schon erkannt, daß die DataReceived-Methode in einem eigenen Thread läuft. Daher bringt es nichts, dort eine Exception zu werfen, da du sie niemals in deinem (GUI-)Thread fangen kannst (außer du 'invoke'st diese Exception: Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke)).
    Besser ist es daher, Ereignisse dafür zu benutzen.

    Außerdem schreibst du, daß du selbst keine Threads einsetzt. Ich hoffe, dir ist aber klar, daß wenn jemand deine Klasse in einem eigenen GUI-Programm einsetzt, er selber dafür einen Thread erzeugen müßte, denn deine Write-Methode würde ja sonst den GUI-Thread blockieren (s. z.B. Warum blockiert mein GUI?).



  • Th69 schrieb:

    Du hast doch schon erkannt, daß die DataReceived-Methode in einem eigenen Thread läuft. Daher bringt es nichts, dort eine Exception zu werfen, da du sie niemals in deinem (GUI-)Thread fangen kannst (außer du 'invoke'st diese Exception: Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke)).
    Besser ist es daher, Ereignisse dafür zu benutzen.

    Hm Ok, also werd ich wohl um einen OnError-Event nicht herumkommen, das hatte ich befürchtet.

    Th69 schrieb:

    Außerdem schreibst du, daß du selbst keine Threads einsetzt. Ich hoffe, dir ist aber klar, daß wenn jemand deine Klasse in einem eigenen GUI-Programm einsetzt, er selber dafür einen Thread erzeugen müßte, denn deine Write-Methode würde ja sonst den GUI-Thread blockieren (s. z.B. Warum blockiert mein GUI?).

    Ja das ist mir schon klar. Ich will dem Benutzer halt gerne die Wahlfreiheit lassen: Wenn er blockieren will soll er das können. Das ganze dann selbst in einen eigenen thread auszulagern ist dann ja easy (außerdem ist es so unabhängig vom "Parallelisierungs-Werkzeug" wenn man so will :D).


Anmelden zum Antworten