callback auf funktion außerhalb des scopes

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Und noch eines. Nach unten hin kann man ja Aufrufe machen und auch Rückgabewerte bekommen. Aber nach oben hin darf das nicht geschehen. Globale Variablen, damit man von oben Zustände pollen kann oder Callbacks in die unteren Layers reinmatschen widerspricht jeder gesunden Programmierphilosophie. Man möchte ja in der GUI eventuell anzeigen, welche Art von Daten empfangen wurden oder wie weit deren Verarbeitung ist. Zustände werden üblicherweise durch Signale gemeldet und nicht durch sonstige Programmverflechtungen zwischen Software Layers und gar durch Aufrufe von unten nach oben. Wer meint, dass man das aber doch so machen sollte, und dann das auch noch auf Teufel komm raus verteidigt, dem ist wohl kaum mehr zu helfen.
__deets__
User
Beiträge: 14541
Registriert: Mittwoch 14. Oktober 2015, 14:29

Callbacks der GUI die aus der Datenschicht aufgerufen sind böse(TM), aber callbacks die man in deiner globalen Callbackschleuder registriert sind gut(TM). Is klar.

Es ist ja schon ausreichend belegt, dass dir das nachdenken über globalen Zustand, und wie man ihn möglichst vermeidet, nicht gegeben ist. Muss ja nicht.

Nur dieses bestimmte Bessergewisse immer....
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@chwin: bevor Du hier völlig den Durchblick verlierst: Für die Aussenwelt kannst Du verschiedene Receiver anlegen, gleiches gilt für die Datenbank. Solange beide die gleichen Schnittstellen aufweisen (receive(), store()), brauchst Du den Processor nicht zu ändern.
Das sieht ähnlich dem was BlackJack schon zeigte, als Python-Pseudocode etwa so aus:

Code: Alles auswählen

class Receiver:

    def receive(self):
        # wait for data and return them
        return data
    

class Database:

    def store(self, data)
        # store the data


class Processor:

    def __init__(self, database, receiver):
        self.db = database
        self.receiver = receiver
        
    def process(self, data):
        # do something with the data and return the result
        return result
        
    def run(self):
        while True:
            data = self.receiver.receive()
            result = self.process(data)
            self.db.store(result)


def main():
    database = Database()    
    receiver = Receiver()
    processor = Processor(database, receiver)
    processor.run()
Mehr brauchst Du erst einmal nicht.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

__deets__ hat geschrieben:Callbacks der GUI die aus der Datenschicht aufgerufen sind böse(TM), aber callbacks die man in deiner globalen Callbackschleuder registriert sind gut(TM). Is klar.
Es ist ein Unterschied, ob man aufruft publish(ID,parameter) oder ob man zuerst alle möglichen Callbacks in ein Modul reinmatscht, und dann die Funktionsreferenzen in allerlei Variablen oder Attributen speichert. Außerdem ist auch denkbar, dass später noch andere auch diese Info haben wollen. Ist doch einfacher, wenn man die Funktionsreferenzen nicht an vielen Stelle zwischenspeichern muss und dann ist es überhaupt nicht OK, dass man dazu auch noch diverse Methoden in diesem Modul aufrufen muss, um die Funktionsreferenzen zu übergeben.

Es reicht, wenn die unteren Layer ein publish machen und die oberen einfach ein bind oder connect - sicher davon schon etwas gehört - oder hier war es dann subscribe genannt. Ein connect oder bind empfindest Du dann seltsamerweise nicht als globale Datenscheuder, oder wie ist es damit?

Wie kommst Du jetzt auf globale Datenschleuder? Wenn es über einen lokalen Rahmen hinausgeht, dann soll man auch nicht für alles denselben Eventbroker nehmen. Für meinen GuiDesigner habe ich vielleicht nur 20 Messages gebraucht oder auch bis zu dreißig Messages. Auch bis zu hundert sollten noch übersichtlich sein, passen ja auf zwei Blatt Papier. Das kann man vergleichen mit einem lokalen Ortsnetz, sozusagen ein Minidorf. Für hundert solcher Dörfer nimmt man dann even hundert Eventbroker und einen als Fernnetz. Wenn es mehr wird, nimmt man noch international mit hundert solch kleiner Länder. Hat man hundert Länder mit hundert Gemeinden mit je bis zu hundert Messages sagen wir mal je ein Eventbroker für bis zu zehn Module. Dann hat man insgesamt bis zu hunderttausend Module mit bis zu einer Million Messages mit bis zu 10101 Eventbroker. Da hat man dann doch einen guten Überblick und kein Eventbroker ist global. Da macht man die Signale einfach mit bis zu drei IDs und dann noch das nationale oder internationale Amt, etwa '0' oder '00'. Damit ist es übersichtlich strukturiert.

Es spricht auch nichts dagegen, dass jedes Modul einen eigenen Eventbroker mit eigener Vorwahl hat.
__deets__
User
Beiträge: 14541
Registriert: Mittwoch 14. Oktober 2015, 14:29

Und wenn man jedem Event seinen eigenen Broker gibt, dann heisst das "Observable Pattern" und findet sich zB in der Gestalt von Signal/Slots in Qt. Und dann wird deine "Vorwahl" einfach nur das Objekt selbst, und du bist endlich da angekommen, wo der Rest sein Mitte der 90er schon war.

Und da wird auch nicht "gematscht" wie du so schoen herablassend sagst (aber spaeter wieder Beschweren ueber die Formulierungen anderer), sondern ein sauberes loose-coupling erreicht, bei dem Objekte einer Schicht die einer anderen ueber diverse Zustandsaenderungen informieren. Das *muss* aber nicht besser sein als das man periodisch (oder aufgrund anderer Ereignisse) einfach *abfragt*, was denn so Phase ist.

Denn das Observable-Pattern hat so seine Tuecken, wenn man implizit Code schreibt, der sich auf eine eigentlich nirgends klar definierte Ereignisreihenfolge verlaesst. Ist also nicht so ganz simpel.

Ja, Events sind ein hilfreiches Ding. Manchmal. Mit globalem Broker (wie du sie hier immer propagierst, anderslautendes ist immer nur Gerede, noch nie gesehen) aber nur fuer sehr, sehr eingeschraenkte Zwecke. Und darum bei weitem nicht das von dir propagierte Allheilmittel.
chwin
User
Beiträge: 16
Registriert: Freitag 4. Juli 2014, 15:07

Hallo

oh mann, da teste ich die ersten zwei Antworten und komme nach einen Tag zurück...
Damit hätte ich jetzt nicht gerechnet. :shock:

@kbr Das sieht in etwas so aus wie ich es jetzt plane. Ich bin aber gerade am überlegen ob ich mich nicht einfach nach einer anderen AMQP Library umsehen. Das ganze Problem ist ja nur entstanden weil AMQPStorm zwingend einen Callback will.

Mir wäre es viel lieber ich könnte pollen.

Evtl denke ich ja total falsch und zäume das Pferd von hinten auf.

Aber, mein Wunsch ist es die Kontrolle selber zu behalten.
Ich möchte eine Nachricht abrufen, und diese Schritt für Schritt per funktion-call und return-value verarbeiten.
So dass ich viele kleine eigenständige Funktionen habe für die ich einfach Tests schreiben kann.

Leider dreht diese Callback Geschichte alles um.

Der Callback ruft meine Verarbeitsungsfunktion auf und und die Nachricht hangelt sich durch einen Funktionsbaum. Am Ende wird ein "OK" und das ACK den Baum zurück nach oben geschickt.

So habe ich viele Funktionen die voneinander abhängen und für dich ich nicht so einfach Tests schreiben kann.

Im Prinzip ist die AMQP Lib jetzt der kontrollierende, zentrale Punkt des Programms.

Ich werde dieses mal mehr Zeit in die Planung investieren.
Die logik steht und Funktioniert. Ich brauche nur ein neues Gerüst.


auch wenn ich den tieferen Hintergrund eures Streits nicht nachvollziehen kann
vielen Dank für die Mühe
__deets__
User
Beiträge: 14541
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich sehe da jetzt eigentlich keinen Widerspruch.

Das ein AMPQ-System einen Callback erwartet ist jetzt nicht weiter verwunderlich. Der Natur nach handelt es sich ja um Ereignisse, die bekommt man ueblicherweise so.

Das deren Verarbeitung dann aber komplizierter ist, weil es aus einem Callback kommt - das leuchtet mir nicht ein.

Du kannst doch deinen gewuenschten sequentiellen Ablauf programmieren, mit beliebigen Transformationen etc

Code: Alles auswählen

def process(event):
      a = transform(event)
      b = transform_more(a)
      ...
Jede der beteiligten Funktionen ist einzeln testbar. Nur die gesamte process Funktion wird dann halt statt vom Test aufzurufen in der AMPQ als Callback registiert.

Wie stellst du dir das anders vor?
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

__deets__ hat geschrieben:Und wenn man jedem Event seinen eigenen Broker gibt, dann heisst das "Observable Pattern" und findet sich zB in der Gestalt von Signal/Slots in Qt. Und dann wird deine "Vorwahl" einfach nur das Objekt selbst, und du bist endlich da angekommen, wo der Rest sein Mitte der 90er schon war.
Nö, vielleicht ist da endlich der Rest da angekommen, dass ich nichts Außergewöhnliches und Unerhörtes gebracht hatte, sondern nur etwas völlig Normales, das seit Mitte der 90er in sehr vielen Bereichen eingesetzt wird.
__deets__ hat geschrieben:Und da wird auch nicht "gematscht" wie du so schoen herablassend sagst (aber spaeter wieder Beschweren ueber die Formulierungen anderer), sondern ein sauberes loose-coupling erreicht, bei dem Objekte einer Schicht die einer anderen ueber diverse Zustandsaenderungen informieren. Das *muss* aber nicht besser sein als das man periodisch (oder aufgrund anderer Ereignisse) einfach *abfragt*, was denn so Phase ist.
Natürlich wird da nichts gematscht, sondern man hält sich an die bereits vorhandenen sauberen Schnittstellen. Kein Mensch käme auf die Idee in den Source Code von Qt seine Callbacks für eine Anwendung hineinzumantschen. Das wäre nämlich gematscht und für so ähnliches woanders bin ich auch nicht dafür.

[quote="__deets__"Denn das Observable-Pattern hat so seine Tuecken, wenn man implizit Code schreibt, der sich auf eine eigentlich nirgends klar definierte Ereignisreihenfolge verlaesst. Ist also nicht so ganz simpel.[/quote]Man muß eben gewohnt sein, ereignisorientiert zu programmieren und darf nicht erwarten, dass nach Senden von etwas dann gleich unmittelbar auch schon das Ergebnis da ist. Im Normalfall laufen Events auch über eine Queue
__deets__ hat geschrieben:Ja, Events sind ein hilfreiches Ding. Manchmal. Mit globalem Broker (wie du sie hier immer propagierst, anderslautendes ist immer nur Gerede, noch nie gesehen) aber nur fuer sehr, sehr eingeschraenkte Zwecke. Und darum bei weitem nicht das von dir propagierte Allheilmittel.
Ob etwas global ist kommt darauf an, ob man nur einen Knoten hat, also nur Ortsnetz oder viele. Für kleine Anwendungen von ein paar Modulen reicht wohl ein Eventbroker vollständig aus. Und mit Messages wird überall gearbeitet, die ganzen Systeme im Automotiv Bereich im Militärischen Bereich in luft und Raumfahrt arbeiten damit, da gibt es verschioedene Busse, wie etwa CAN, MOST, Flexray über die dann Signale übertragen werden. Die haben dann auch einen Physikalischen Layer und Protokollstacks und arbeiten mit Qeues. Ich hatte hier nur einen ganz simplen mit indirektem Funktionsaufruf gezeigt, denn eine Qeue und weitere Features erleichtern nicht das Verständnis. Hast ja gelesen, dass jemand die Zwei Zeilen Code, nämlich Funktionsdefinition mit Parametern und dann Aufruf über Dictionary bereits als Komplexitätsmonster bezeichnet hat.

Und ein Allheilmittel ist so etwas keineswegs. Man kann dann zwar den Code leicht in andere Module verteilen. Ob er dabei aber übersichtlicher wird ist fraglich.

Da sollte man vielleicht doch anders vorgehen. Zuerst einmal in der Source verschiedene Bereiche machen und dort hineinmoven, was zu diesem Bereich gehört. Und wenn man dann damit letztendlich zufrieden ist, kann man die Auslagerung vorbereiten.

Und eine solche Vorbereitung sähe dann im Mainscript so aus:

Code: Alles auswählen

# import empfang
# import speichern
import verarbeitung

verarbeitung.init(verarbeitung = verarbeitung,
                  empfang = verarbeitung,
                  speichern = verarbeitung)
Und wenn man diese Werte dann in globalen Variablen speichert, kann man schon einmal die Aufrufe richtig benennen. Und wenn sie richtig benannt sind, kann man dann aufteilen.

Ob der Code dann schon schön ist, ist immer noch fraglich. Aber es schadet nichts über Messages nachzudenken. Nicht unbedingt, dass man das implementieren soll. Aber es wäre gut über sinnvolle Schnittstellen und sinnvolle Benennungen nachzudenken. Und kann solche sinnvollen Schnittstellen dann ja auch als Mehodeninterfaces implementieren.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@chwin: Ein Callback ist aber auch leicht zu handhaben:

Code: Alles auswählen

class Database:

    def store(self, data):
        # store the data
        
        
class Processor:

    def process(self, message):
        # process message and return the result
        return result


class MessageHandler:

    def __init__(self, processor, database):
        self.processor = processor
        self.database = database
        
    def __call__(self, message):
        result = self.processor.process(message)
        self.database.store(result)
und gemäß https://github.com/eandersson/amqpstorm ... onsumer.py in Zeile 41:

Code: Alles auswählen

...
#channel.basic.consume(on_message, 'simple_queue', no_ack=False)
message_handler = MessageHandler(Processor(), Database())
channel.basic.consume(message_handler, 'simple_queue', no_ack=False)
...
Processor und Database können auch hier leicht ausgetauscht werden, solange die Methoden process und store vorhanden bleiben. Falls es die Komplexität der Aufgabe nicht erfordert, könnten Processor und Database auch durch Funktionen ersetzt werden. Oder auch der MessageHandler, wie bei __deets__. Testen ist gleichfalls kein Problem.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Wobei mir das immer noch zu sehr Java wäre. Warum nicht einfach Funktionen verwenden?
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

snafu hat geschrieben:Wobei mir das immer noch zu sehr Java wäre. Warum nicht einfach Funktionen verwenden?
Im vorliegenden Fall wurde das mit Funktionen oder Methoden gemacht. Das ist aber nicht immer sinnvoll. Ich hatte nämlich auch mal so etwas gemacht mit Empfang, Verarbeitung und Speichern. Dabei konnte man auch etwa Videodateien - auch 2 GB kein Problem - vom PC auf das Smartphone übertragen. Klar dass man vom PC keine Funktion auf dem Smartphone aufrufen kann. Ebenso aber hatte ich die Software in Threads aufgegliedert. Der Empfang soll ja nicht zuerst die Videodatei in den Hauptspeicher schreiben - außerdem hat mein Smartphone eh nicht soviel -, während die anderen warten. Und wenn dann die Daten in ein File geschrieben werden, soll natürlich der Empfang nicht warten. Daher schrieb der Empfang die Daten in eine Queue und triggerte dann die Verarbeitung. Der Empfang schrieb dann weiter in die Queue bis zu einer Obergrenze und legte sich dann schlafen. Bei Erreichung einer Untergrenze in der Queue wurde der Empfang dann wieder aufgeweckt und forderte weitere Daten vom PC an. Natürlich wurde auch da nicht erst angefangen, wieder aus einer Datei zu lesen, sondern auch da war man nicht untätig und hatte die Daten bereits in einer Queue gebuffert. Alle Komponenten sollten arbeiten, wenn es was zu tun gab und auf andere sollte dabei nicht gewartet werden, damit das System optimal läuft.

Und bei Threads ruft man nicht einfach Funktionen in anderen Threads auf. Daher einfach Message gesandt. Und die triggerte dann das Event im anderen Thread. Da brauchte man dann gar nicht extra ein Eventhandling programmieren, das das bereits im Eventbroker integriert war.

Also: Callbackaufruf auf einem Tablet in China bekommt man mit Funktionsaufruf einfach nicht hin.
Zuletzt geändert von Alfons Mittelmeyer am Freitag 1. September 2017, 20:40, insgesamt 1-mal geändert.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: was hat jetzt dieser Beitrag, dass Du auch bei anderen Problemen viel zu komplizierte Lösungen hast, mit dem eigentlichen Thema zu tun?
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@Alfons Mittelmeyer: was hat jetzt dieser Beitrag, dass Du auch bei anderen Problemen viel zu komplizierte Lösungen hast, mit dem eigentlichen Thema zu tun?
Es hat damit etwas zu tun, dass die Software in nur einem Thread zu haben, nicht immer eine optimale Lösung ist. Mit Messages ist man auch davon unabhängig.

Und außerdem war das keine komplizierte Lösung, die ich geschildert hatte sondern eine optimale. Ein schlechte Lösung ist in manchen Fällen, wenn der Empfang nicht empfangen kann, während die Daten gespeichert werden.

Es kommt eben darauf an, um welche Daten es sich handelt und was damit geschehen soll. Ist es ein Temperaturwert von einem Thermometer Nullo Problemo. Ist es ein Gigabyte und soll das kompiliert werden mit einer Compilierzeit von 30 Stunden, dann sieht es ganz anders aus.
chwin
User
Beiträge: 16
Registriert: Freitag 4. Juli 2014, 15:07

Hallo

ich muss zugeben, Eure Diskussion ist recht weit außerhalb meines Verständnisbereichs, daher gehe ich da auch nicht drauf ein.
Evtl hilft Euch ja die genauere Beschreibung meines Problems Euch auf den "richtigen" Weg zu einigen :D

Bei meiner Anwendung geht es um Fileserver, die ich betreue.

Wir haben inzwischen so viele Files, das ein Treewalk um die Daten die zu sichern sind zu identifizieren nicht mehr funktioniert. Die Laufzeit eines Treewalks liegt bei etwa 30h. Wir sprechen hier von 350+Mio Files. Meine Aufgabe ist es das Backup sicherzustellen.

Also hatte ich mir überlegt, das der Filer ja eine Debug Schnittstelle hat, über die ich alle Filesystem Events bekomme hat.

Ich protokolliere alle Filezugriffe mit, führe in einer Datenbank ein virtuelles Dateisystem und weiss permanent, welche Files sich geändert haben. Einfach durch eine Datenbankabfrage.
Das ganze funktioniert und läuft.
Zzt haben wir max. 3000 Events pro Sekunde. Im Mittel (8-19Uhr) liegen wir bei 250/s
Die einzelnen Nachrichten haben eine Größe von etwa 1 bis 2 k. Einige wenige sind aber auch bis zu 5k groß.

Mir ist jetzt aber eingefallen, das ich mit all den Daten noch viel mehr machen kann.
Ich wollte also das ganze erweitern. Dabei ist alles zusammengebrochen.
Jede Zeile Code hing irgendwie mit jeder anderen zusammen.

Daher will ich es jetzt in Module zerlegen.
Der Wechsel von MySQL auf SQLite hat den Ausschlag gegeben. Das war ein riesen Akt.

Jetzt will ich die Daten noch nach anderen Gesichtspunkten auswerten und im Verarbeitsungsmodul sozusagen Plugins anstöpseln können.

Die ersten Hinweise, die ich bekommen haben waren schon sehr hilfreich. Ich werde den Ansatz ohne Message Broker verfolgen, einfach weil ich ihn gedanklich besser nachvollziehen kann.

Bis der Umbau fertig ist, wird es aber noch ein wenig dauern.



Vielen Dank für die viele Zeit
Antworten