Arbeiten mit Events.



  • Dass das von der GUI-API abhängig ist, dass C++ da nicht wirklich etwas anbieten kann (außer mitzuhelfen durch thread-sichere Queues oder ähnlichem, was ja mit einem der nächsten C++-Standard mitkommt, glaube ich gelesen zu haben)



  • Bei synchronen Events wird im Programm immer nur ein Event gleichzeitig abgearbeitet (das Betriebssystem arbeitet im Hintergrund noch an gestellten Aufgaben). Das hat den gigantischen Vorteil, dass man keine Dataraces hat, keine Locks braucht und damit auch keine Deadlocks bauen kann. Der Nachteil ist, dass sie etwas umständlich zu programmieren sind.
    Threads sind viel einfacher zu programmieren und haben bessere Performance, aber man kriegt Probleme mit Locks und Dataraces.
    Asynchrone Events sind umständlich zu programmieren und haben zusätzlich Dataraces und Deadlockprobleme. Wieso sollte man sowas jemals wollen?



  • Asynchrone Events sind umständlich zu programmieren und haben zusätzlich Dataraces und Deadlockprobleme. Wieso sollte man sowas jemals wollen?

    Wenn man Devices wie einen Comport hat, Daten liesst und kein Busy waiting haben moechte bzw. wenn ich Pakete verarbeiten will, wenn ich Zeit habe ...



  • Frolo schrieb:

    Sry kein Raise: http://www.delphi-treff.de/tutorials/vcl/komponenten-entwicklen/ereignisse-events/

    Das sieht nach typischem GUI-Kram. Was Du meinst ist nicht "raise" sondern "delegates". QT regelt sowas mit seinem Signal-Slot-Konzept. Wenn Du eh schon QT verwendest, dann schau dir das doch mal an.

    Der "GUI-Kram" funktioniert in der Regel so, dass es da einen "GUI-Thread" gibt, der im Wesentlichen auf irgendwelche Ereignisse wie Benutzereingaben, Mouseklicks (etc) wartet und entsprechende Handler, die vorher registriert werden können, aufruft. Wenn du jetzt keine solche GUI-Anwendung baust, dann musst du dir ggf selbst so einen Event-Loop bauen oder es irgendwie anders machen. Für Netzwerk-Kram gibt's da sicherlich auch schon Sachen.

    So etwas ähnliches wie "delegates" kannst du in C++11 auch mit std::function und Lambdas bekommen:

    #include <iostream>
    #include <functional>
    
    void machdat(std::function<void(int)> func)
    {
        func(42);
    }
    
    struct clazz
    {
        void blah(int number) const
        { std::cout << number << std::endl; }
    };
    
    int main()
    {
        clazz obj;
        machdat([&](int num){obj.blah(num);});
        return 0;
    }
    


  • knivil schrieb:

    Asynchrone Events sind umständlich zu programmieren und haben zusätzlich Dataraces und Deadlockprobleme. Wieso sollte man sowas jemals wollen?

    Wenn man Devices wie einen Comport hat, Daten liesst und kein Busy waiting haben moechte bzw. wenn ich Pakete verarbeiten will, wenn ich Zeit habe ...

    dann verwendet man entweder eine "blocking API" (wo ein Leseversuch solange blockiert, bis was da ist, ohne dass das CPU-Zyklen verbrät) innerhalb eines eigenen Threads ... oder arbeitet mit 'nem Event-Loop, der, wenn was anliegt, Handler aufruft, die schnell fertig werden und dabei ggf neue Events anstoßen. Das ist recht ätzend zu programmieren, weil man sich irgendwo einen Zustand merken muss. Deswegen hat Microsoft dafür auch eine Lösung für C# zu bieten: async/Task<>. Die Programme sehen da fast so aus, wie die Versionen, die blockierende APIs verwenden. Unter der Haube baut der Compiler dann aus den Task<> - return enden async -Funktionen einen Zustandsautomaten und macht die Funktion damit "resumable". In C++ haben wir so etwas aber (noch) nicht. Es gibt aber immerhin ein Proposal für "resumable functions".

    Ich wüsst aber auch gerne, wie man das eigentlich "richtig" in C++ machen würde, mit den Mitteln, die aktuell zur Verfügung stehen. Das ist einfach nicht mein Gebiet und hab deswegen da nicht die Erfahrung und kenne auch kaum Bibliotheken in der Hinsicht.

    Ich hatte mal was von dem "proactor model" gehört, was Boost.ASIO zu Grunde legen soll. Aber ich hatte bisher keinen Nerv, mir das alles durchzulesen.



  • In C++ haben wir so etwas aber (noch) nicht. Es gibt aber immerhin ein Proposal für "resumable functions".

    Wenn du Coroutinen fuer C++ meinst, die halte ich persoenlich fuer Bockmist. Aber vielleicht gefaellt es mir, wenn es fertig ist.

    Leseversuch solange blockiert

    Kommt beispielsweise nicht in Frage, wenn der Anwender die Anwendung schliesst, aber ein Read in einem anderen Thread auf sich warten laesst. boost::asio ist fuer Comports Overkill.

    Ich wüsst aber auch gerne, wie man das eigentlich "richtig" in C++ machen würde

    Ich verwende das klassische Schema: select + timeout in einem eigenen Thread als "Service".

    Deswegen hat Microsoft dafür auch eine Lösung für C# zu bieten: async/Task<>.

    Sind sie kill-safe?



  • Was ist an boost::asio eigentlich Overkill in Bezug auf COM-Ports? Wenn man sich vor Augen führt, dass boost::asio zB. unter Windows eigentlich nur ein Wrapper um IO-Completion-Ports ist (plus ein bissl Firlefanz für Dinge, die unter XP mit IOCPs noch nicht möglich waren, wie zB. Timer), dann kann der Overkill doch eigentlich gar nicht so groß sein. Ab Vista+ habe ich immer (verhältnismäßig) sehr gerne mit IOCPs gearbeitet. Die Frage ist nur, ob man sich boost antut oder nicht... Sieht halt immer komplizierter aus, als es eigentlich ist, dafür hat man keine Portierarbeit und die Sache ist gut getestet.



  • Okay, hab mir das von @krümelkacker angeschaut (noch nicht getestet). Ich habe generell ja bis jetzt nur mit den Delphi Events gearbeitet, aber nach gründlichem Nachdenken, bin ich darauf gekommen, dass Events in Delphi doch synchron sind. Wenn ich ne while(1)-Schleife in ein Delphiprogramm schreibe und beispielsweise in ein ButtonClick Event schreibe, hängt sich das Programm (die GUI) auf. Deshalb denke ich, dass das ganze synchron ist.

    Jetzt nochmal zu meiner Frage: Ist es durch den Vorschlag von "krümelkacker" möglich so etwas ähnliches zu machen:

    //PSEUDO-CODE
    MeinTCPClient tcp1;
    
    //-------------
    
    void MeineProc {
     //die soll aufgerufen werden, wenn ein neuer Client connected
    }
    
    tcp1.OnConnect = MeineProc;
    

    So würde ich mir das im Prinzip wünschen, dass es später eben genau so einfach ist. Kann ich das so machen (oder ähnlich) und wie müsste ich das dann innerhalb des Objekts umsetzen?



  • #include <functional>
    
    //...
    std::function<void()> handler;
    void somefunc() {}
    handler = somefunc;
    handler();
    


  • Was ist an boost::asio eigentlich Overkill in Bezug auf COM-Ports?

    Weil man sich die Abhaengigkeit zu boost hereinholt, obwohl man das Beispiel aus der MSDN fuer Comports quasi abschreiben kann.



  • Frolo schrieb:

    Okay, hab mir das von @krümelkacker angeschaut (noch nicht getestet). Ich habe generell ja bis jetzt nur mit den Delphi Events gearbeitet, aber nach gründlichem Nachdenken, bin ich darauf gekommen, dass Events in Delphi doch synchron sind.

    Du sagst "Events" meinst aber was anderes: Delegates. Über Delegates werden Handler festgelegt, die von einem Event-Loop aus aufgerufen werden.

    Frolo schrieb:

    Wenn ich ne while(1)-Schleife in ein Delphiprogramm schreibe und beispielsweise in ein ButtonClick Event schreibe, hängt sich das Programm (die GUI) auf. Deshalb denke ich, dass das ganze synchron ist.

    Du schreibst kein "Event". Du schreibst einen Handler, einen Handler der vom Event-Loop aus aufgerufen wird. Wenn der Handler dann eine Endlosschleife enthält und nicht zum Event-Loop zurückkehrt, dann kann das Program nicht mehr auf weitere Events reagieren. Um die GUI "ansprechbar" zu halten, müssen Handler also schnell abgearbeitet werden.

    Frolo schrieb:

    Jetzt nochmal zu meiner Frage: Ist es durch den Vorschlag von "krümelkacker" möglich so etwas ähnliches zu machen:

    Du wolltest wissen, wie man delegates in C++ bekommen würde, wobei du statt "delegates" eben "events" gesagt hast. Dazu hatte ich ein Beispiel. Ein Event-Loop ist u.a. irgendwo in QT implementiert. Sowas kann man sich selbst bauen. Muss man möglicherweise aber nicht. Da hört dann auch meine Expertise auf.

    Frolo schrieb:

    //PSEUDO-CODE
    MeinTCPClient tcp1;
    
    //-------------
    
    void MeineProc {
     //die soll aufgerufen werden, wenn ein neuer Client connected
    }
    
    tcp1.OnConnect = MeineProc;
    

    So würde ich mir das im Prinzip wünschen, dass es später eben genau so einfach ist. Kann ich das so machen (oder ähnlich) und wie müsste ich das dann innerhalb des Objekts umsetzen?

    Dann brauchst du einen Event-Loop, der mit einer Message-Queue arbeitet, wo man solche Events reinstecken kann.

    knivil schrieb:

    Was ist an boost::asio eigentlich Overkill in Bezug auf COM-Ports?

    Weil man sich die Abhaengigkeit zu boost hereinholt, obwohl man das Beispiel aus der MSDN fuer Comports quasi abschreiben kann.

    Verlink das doch mal. Ich hatte anfang dieses Jahres da nur mal mit einer "blocking API" gearbeitet. Und das war ein recht simples C-Interface, wo ich erstmal einen C++-Wrapper für RAII drumherum gebaut habe. Das ganze dann noch verPIMPLt, so das im Header nix System-spezifisches mehr steht.



  • http://msdn.microsoft.com/en-us/library/ff802693.aspx war meine Basis fuer meine eigene Comport-Klasse. Nun einfach sieht anders aus, aber was solls ... Das Hauptproblem bei overlapped IO ist, dass ich benachrichtigt werde, wenn meine Operation abgeschlossen ist. Es ist schwierig, eine gestartete Operation korrekt abzubrechen. Das Konzept unter Unix ist, ich werde benachrichtig wenn etwas verfuegbar ist. Das Problem des Abbrechens gibt es dort nicht.



  • Ich finde die Unterteilung synchron vs. asynchron sinnlos.
    Event-Callbacks werden immer synchron aufgerufen, die Frage ist nur synchron mit was.
    Das muss man halt wissen, und dann kann man das Programm entsprechend darauf auslegen.

    Falls man sich nicht mit Multithreading auskennt, dann wäre es natürlich vorteilhaft wenn es nur einen Thread gibt, und von diesem auch die Event-Callbacks ausgeführt werden.



  • knivil schrieb:

    http://msdn.microsoft.com/en-us/library/ff802693.aspx war meine Basis fuer meine eigene Comport-Klasse. Nun einfach sieht anders aus, aber was solls ... Das Hauptproblem bei overlapped IO ist, dass ich benachrichtigt werde, wenn meine Operation abgeschlossen ist. Es ist schwierig, eine gestartete Operation korrekt abzubrechen. Das Konzept unter Unix ist, ich werde benachrichtig wenn etwas verfuegbar ist. Das Problem des Abbrechens gibt es dort nicht.

    Habe jetzt nicht auf den Link geklickt, aber ich frage mich gerade, wo der Unterschied zwischen Windows und Unix Deiner Beschreibung nach sein soll. die erklärung ist nämlich fast gleich, bis auf dass du einmal "wenn die Operation abgeschlossen ist" und das andere mal "wenn etas verfügbar ist" sagst. Meinst du bei "etwas" hier _irgend_ etwas? Also eine Queue, wo alle Events reinkommen statt eines Callback-Mechanismus bei Windows?



  • Er meint reactive IO vs. proactive IO.

    Reactive heisst dein Programm lässt sich benachrichtigen wenn es was machen kann, und reagiert auf diese Benachrichtigung indem es was macht.
    select() -> read(non-blocking aber synchron, Daten stehen bereits in eine Puffer des OS und werden direkt im read-Auftuf in den Puffer das Applikation kopiert).

    Proactive heisst dein Programm sagt dem OS vorab schonmal was es machen soll, und bekommt dann eine Benachrichtigung vom OS wenn die Operation abgeschlossen wurde (egal ob jetzt mit Fehler oder Erfolg).
    BeginRead() -> Daten werden irgendwann im Hintergrund gelesen und in den Puffer der Applikation kopiert -> Callback -> EndRead()

    Der Unterschied was Cancellation angeht...

    Bei reactive gibt es keine "ausständigen" IOs, d.h. es gibt auch nichts zu canceln. Nur dann wenn man die "jetzt ginge was" Benachrichtigungen auf einem (oder mehreren) anderen Threads annimmt, als der wo man ein Objekt zerstören will, muss man etwas aufpassen.

    Bei proactive dagegen hast du einen oder mehrere IOs ausständig, die jederzeit abgeschlossen werden können. Wenn du dein Objekt zerstören willst, musst du nun entweder sicherstellen dass vorher alle Benachrichtigungen verarbeitet wurden, oder dass du bei den Benachrichtigungen irgendwie mitbekommst dass das Objekt bereits zerstört wurde (oder dabei ist zerstört zu werden -- z.B. über die Verwendung eines weak_ptr ).
    Beide Varianten sind allerdings lästig, und auch nicht ganz trivial fehlerfrei hinzubekommen.
    (Der Puffer muss natürlich auch gültig bleiben bis die Operation abgeschlossen wurde.)

    Wobei man auch unter Windows reactive IO verwenden kann. Und auch unter Linux proactive IO.



  • Okay, eure Antworten waren sehr hilfreich! Ich kannte bisher den Unterschied zwischen Events und Delegates leider nicht, ihr habt mir da sehr geholfen. Was ich mich nun gefragt hab ist folgendes:

    //PSEUDO-CODE
    MeinTCPClient tcp1;
    
    //-------------
    
    void MeineProc {
     //die soll aufgerufen werden, wenn ein neuer Client connected
    }
    
    tcp1.OnConnect = MeineProc;
    

    Wenn das mein Code wäre, könnte ich doch innerhalb der Klasse "MeinTCPClient" die Methode (ich glaub ihr habt das Handler genannt), die ich hier übergebe (Meine Proc) in einem anderen Thread ausführen oder? [Aber so, dass hierbei die Klasse und nicht etwa meine main.cpp steuert, ob das ganze synchron oder asynchron zum main - Programm läuft. Ich hoffe ihr versteht, was ich meine. Vielen Dank!



  • hustbaer schrieb:

    Er meint reactive IO vs. proactive IO. [...]

    Ok, danke, jetzt habe ich verstanden, was ihr meint. Hatte nur eine vage Idee, wofür "select" da ist.


Anmelden zum Antworten