Gibt es nicht fangbare Exceptions?


  • Administrator

    Hallo zusammen,

    Der Threadtitel ist seltsam, aber mir fiel nichts besseres ein und bevor ihr jetzt einfach mit "Nein" antwortet, solltet ihr die Problemstellung durchlesen 🙂

    Also zum Problem:
    Wir haben eine Bibliothek B und ein Program P. P übergibt B ein Delegate zu einer eigenen Funktion, welche B aufrufen soll. B ruft diese Funktion auf und P ruft darin weitere Funktionen von B auf. Nun kann in einer dieser weiteren Funktionen ein Fehler auftreten. Damit B in einem definierten Zustand bleibt, darf P auf keinen Fall die geworfene Exception in der Delegate-Funktion fangen. Also "graphisch" dargestellt:

    P -> B -> Callback von P -> B -> X
    1    2    3                 4    5
    
    Der Fehler bei 5 muss unbedingt bei 2 ankommen, sonst ist der Zustand undefiniert.
    

    Bisher habe ich dies einfach in der Dokumentation klar erwähnt. Aber Dokumentationen werden ja immer wieder nicht richtig gelesen, weshalb davon eine gewisse Gefahr ausgeht. Daher suche ich nach Möglichkeiten, dies direkt im Code zu erzwingen. Weiss jemand, ob dies irgendwie geht? Allenfalls auch nicht über Exceptions...

    Ich könnte auch noch "relativ einfach" die weiteren Aufrufe von B in der Callback von P schützen und somit den Fehler erneut werfen, wenn versucht wird, weitere Funktionen von B aufzurufen. Allerdings gäbe es immer noch Wege dies zu umgehen und daher frage ich mich, ob ich einen solchen Schutz dann überhaupt noch einbauen sollte und nicht lieber sagen sollte: "Selber Schuld, wenn du die Dokumentation nicht liest." 😉

    Grüssli



  • Ich würde die Funktionen einfach in einen Wrapper packen und dort in einen Try-Catch Block laufen lassen. Das heißt alle Aufrufe aus B garantieren mir keine Exception mehr zu werfen da ich sie voher abfange. Alle Exception die in den Wrapper laufen kannst du dann mitloggen und später auswerten nachdem P fertig ist. So kriegt P auch nie eine Exception in die Hand. Ggf kann er ja den Wrapper fragen ob bei einer Funktion ein Fehler aufgetreten ist.


  • Administrator

    Fedaykin schrieb:

    Ich würde die Funktionen einfach in einen Wrapper packen und dort in einen Try-Catch Block laufen lassen. Das heißt alle Aufrufe aus B garantieren mir keine Exception mehr zu werfen da ich sie voher abfange. Alle Exception die in den Wrapper laufen kannst du dann mitloggen und später auswerten nachdem P fertig ist. So kriegt P auch nie eine Exception in die Hand. Ggf kann er ja den Wrapper fragen ob bei einer Funktion ein Fehler aufgetreten ist.

    Ich habe nicht verstanden was du meinst. Dir ist schon klar, dass ich B schreibe und nicht P?

    Grüssli



  • Ja...
    du schreibst B und P kennt B...

    Kurzum es schaut irgendwie so aus

    public class B
    {
       public void foo() //Hier kann eine Exception kommen
       public void bar() //Hier auch
    }
    
    public class BWrapper
    {
    
       public void foo()
       {
         try
         {
           //Call B.foo();
         ..
         }
         catch(Exception e)
         { 
             Log(e);
         }
       }
       ...
    }
    

    Und P bekommt anstatt B den BWrapper in die hand. Kann also Foo und Bar aufrufen kriegt aber nie die Exceptions... da diese im Wrapper schon weggefangen und geloggt werden.



  • Dravere schrieb:

    P -> B -> Callback von P -> B -> X
    1    2    3                 4    5
    
    Der Fehler bei 5 muss unbedingt bei 2 ankommen, sonst ist der Zustand undefiniert.
    

    Du kannst in (4) die Exception fangen, ein Flag setzen dass "B" im Moment "kaputt" ist, und die Exception dann weiter werfen.
    In allen Funktionen von "B" checkst du dieses Flag, und wenn es gesetzt ist, wirfst du sofort eine "bin kaputt" Exception.
    Wenn du in (2) eine Exception fängst, checkst du ob das "B" Objekt als "kaputt" markiert ist, und ob du es wieder "heil" machen kannst. Und tust das ggf.
    Wenn du (3) ohne Exception nach (2) zurückkehrt checkst du ebenso den Zustand von "B", und machst ne Assertion dass "B" nicht "kaputt" ist. Ob du in so einem Fall dann trotzdem versucht "B" wieder "heil" zu machen oder nicht ... kann ich nicht beurteilen ob das schlau wäre.
    (Natürlich kannst du statt eines einfachen Flags in "B" auch Informationen darüber abspeichern was eigentlich schief gegangen ist, falls du diese in (2) brauchst um das Objekt wieder zu reparieren.)

    Was besseres fällt mir dazu nicht ein, da es eben leider (oder Gott-sei-Dank?) keine Exceptions gibt, die nur von bestimmten "berechtigten" Programmteilen gefangen werden können.

    Was du zusätzlich machen könntest (und vermutlich machen solltest), ist, die Exception die du in so einem Fall wirfst nicht von System.Exception abzuleiten. Das verhindert zumindest das Fangen mit catch (Exception ex) .



  • Was du zusätzlich machen könntest (und vermutlich machen solltest), ist, die Exception die du in so einem Fall wirfst nicht von System.Exception abzuleiten.

    Geht das überhaupt? 😕


  • Administrator

    @Fedaykin,
    Das bringt mir aber nichts. Wenn du meinen Pseudo-Callstack anschaust, will ich es nicht bei 4 behandeln. Ich kann es dort gar nicht behandeln. Ich kann es erst bei 2 behandeln. Es muss also irgendwie durch P hindurchgehen. Auch darf P auf keinen Fall irgendeine Funktion aus B aufrufen, wenn die Exception geworfen wird.

    @hustbaer,
    Sowas habe ich mit meinem letzten Absatz gemeint. Ich muss sowieso bei jedem Aufruf einer Funktion in B verschiedene Dinge prüfen. Da könnte ich dann auch so ein Flag prüfen. Leider gibt es dann aber ein paar Probleme beim Multithreading, wodurch man unter Umständen eben trotzdem neben dem Flag durchkommen würde. Ich muss das nochmals genauer nachprüfen, unter welchen Umständen dies genau der Fall wäre.

    Wobei ... 💡 ... muss nochmals die genauen Umstände prüfen 🙂

    interception schrieb:

    Geht das überhaupt? 😕

    In CIL ja, unter C# wird das allerdings vom Kompiler verboten. CIL kann eigentlich alles werfen, was von System.Object abgeleitet ist.
    Deswegen gibt es im übrigen auch noch ein Catch-All in C#:

    try
    {
      // ...
    }
    catch // fängt alles, auch was nicht von System.Exception abgeleitet ist
    {
    }
    

    Grüssli



  • In CIL ja, unter C# wird das allerdings vom Kompiler verboten

    Uff, sorry, das wusste ich nicht.
    In dem Fall zahlt es sich nicht aus, also dafür z.B. extra irgend welchen C++/CLI Code zu schreiben macht IMO wenig Sinn.

    Leider gibt es dann aber ein paar Probleme beim Multithreading, wodurch man unter Umständen eben trotzdem neben dem Flag durchkommen würde. Ich muss das nochmals genauer nachprüfen, unter welchen Umständen dies genau der Fall wäre.

    Dürfen denn anderen Threads auf "B" zugreifen während gerade Step 3/4/5 ausgeführt wird? Wenn ja könnte es tricky werden.
    Wenn nein, bzw. wenn du in (2) selbst synchronisierst sehe ich kein Problem.



  • Ist eine Gabel vor Fehlbenutzung abgesichert. Nein, man sagt einfach, dass man damit keine Augen ausstechen soll. Wenn ich mir die Vorschlaege so durchlese ist ein Kommentar oder Dokumentation die beste Wahl.


  • Administrator

    knivil schrieb:

    Ist eine Gabel vor Fehlbenutzung abgesichert. Nein, man sagt einfach, dass man damit keine Augen ausstechen soll. Wenn ich mir die Vorschlaege so durchlese ist ein Kommentar oder Dokumentation die beste Wahl.

    Unter C oder C++ würde ich dir wahrscheinlich zustimmen, aber bei C# ist die Philosophie eher anders. Ich habe noch keine Bibliothek gefunden, welche einen undefinierten Zustand hatte. Wenn es denn einen gab, dann wurde beim Aufruf einer Funktion eine weitere Exception geworfen, dass das Objekt eben in einem undefiniertem Zustand ist.

    hustbaer schrieb:

    In dem Fall zahlt es sich nicht aus, also dafür z.B. extra irgend welchen C++/CLI Code zu schreiben macht IMO wenig Sinn.

    Ich verwende allerdings bereits C++/CLI 🙂

    hustbaer schrieb:

    Dürfen denn anderen Threads auf "B" zugreifen während gerade Step 3/4/5 ausgeführt wird? Wenn ja könnte es tricky werden.

    Ja können sie. Allerdings war ich mir nicht mehr so klar, wie genau der Zugriff erfolgt, da der Teil der Bibliothek nicht von mir stammt. Habe das nun nochmals nachgeprüft und wie es aussieht, haben sie grundsätzlich getrennte Instanzen. Es gibt zwar eine Überschneidung, welche aber durch einen Fehler nicht betroffen und somit weiterhin in einem gültigen Zustand wäre.

    Ein weiteres Problem ist, dass auch in einem Thread verschiedene Instanzen von B zusammenhängen können. Diese bilden grundsätzlich eine Art von Baum. Und ich habe Zugriff von den Kindknoten zum Wurzelknoten, wodurch ich das Flag in den Wurzelknoten packen kann. Und ich habe herausgefunden, dass ich dadurch gleich noch eine weitere Sicherheitsfunktion einbauen kann, damit man nicht zum falschen Zeitpunkt auf ein B zugreift.

    Also schlussendlich scheint es, als wären alle Probleme gelöst. Vielen Dank für die Diskussion.

    Grüssli


Anmelden zum Antworten