Python3 Asyncio Coroutine und yield from

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.
Antworten
BugsBunny74
User
Beiträge: 8
Registriert: Montag 8. Februar 2016, 13:14

Hallo zusammen,

Seit einigen Tagen beschäftige ich mich mit Python3 und habe große Schwierigkeiten mit dem "Yield from" statement in Verbindung mit dem neuen Asyncio Modul. Ich denke es ist eher ein Verständnisproblem, insofern hoffe ich, dass mir jemand einmal den Weg weisen könnte. Habe zwar einiges gelesen drüber, aber so richtig durch blick ich da momentan leider nicht.

Also, angenommen ich habe eine coroutine

Code: Alles auswählen

@asyncio.coroutine
def create_server(conf):
	#erzeugt ein serverobject und startet den server
	pass
Dazu habe ich eine weitere coroutine die obigen routrine mit yield from aufruft

Code: Alles auswählen

@asyncio.coroutine
def test_connect(conf):
    srv = yield from server.create_server(_sconf, None, None)    
Diese kann ich über

Code: Alles auswählen

loop = asyncio.get_event_loop()
loop.run_until_complete(test_connect(cfg))
loop.close()
starten und der Server wird erzeugt und gestartet. Soweit, so gut, nun möchte ich aber eben diesen Server innerhalb einer Klasse haben. In etwa so:

Code: Alles auswählen


class my_server:
    
    def __init__(self):
        self.__server = None
        self.__config = None
        
    @asyncio.coroutine
    def _get_server(self):
        server = yield from server.create_server(self._config, None, None)
        return "server"

    def start(self, yaml_cfg_file):
    	self.__server = yield from self._get_server()    
Nun instanziere ich diese klasse und rufe die start methode auf

Code: Alles auswählen

m = my_server()
m.start()
Nur wird die Methode Start anscheinend garnicht aufgerufen. Wie gesagt, ich babe große Probleme das Ganze zu verstehen, also dieses neue yield from in Verbindung mit Asyncio. Können solche Coroutinen denn nur über den asyncio event loop aufgerufen werden? Und gibt es überhaupt eine Möglichkeit den über eine coroutine erzeugten server in einer Klasse zu halten? Komme momentan leider garnicht klar und habe schon vor lauter lesen Bretter vorm Kopf, sorry. Also wäre für jede Hilfe dankbar und würde das gerne verstehen. Vielen Dank.

Grüsse,
Bugs
Sirius3
User
Beiträge: 18226
Registriert: Sonntag 21. Oktober 2012, 17:20

@BugsBunny74: wenn Du die Co-Routine einfach so aufrufst, "m.start()", dann wird zwar der Generator erzeugt aber nirgends abgearbeitet. Dafür ist ja die run_until_complete-Funktion aus dem ersten Beispiel zuständig.
BugsBunny74
User
Beiträge: 8
Registriert: Montag 8. Februar 2016, 13:14

Hallo Sirius,

das verstehe ich mittlerweile und bin denke ich auch hinter das Prinzip gekommen. Eine Coroutine bringt ein Ergebnis wenn sie abgearbeitet wurde und ist keine Funktion an sich, deswegen Generator. Somit braucht sie einen Event loop um abgearbeitet zu werden. Ok, mein Problem ist jetzt Folgendes und ich bin mir mittlerweile garnicht mehr so sicher ob das mit Python bzw. mit der gegebenen Architektur überhaupt möglich ist.

Es fällt mir etwas schwer das Problem in Worten zu schildern, denke es lässt sich am besten im Code erklären:

Code: Alles auswählen

class Myserver:
    
    def __init__(self):
        self.__server = None
        self.__config = None
        self.__loop = None

    @asyncio.coroutine
    def _get_server(self, future):
        print ("_get_server")
        srv = yield from server.create_server(self.__config, None, None)
        future.set_result(srv)
        
    def start(self, yaml_cfg_file):
        try:
            with open(yaml_cfg_file) as f:
                self.__config = yaml.safe_load(f.read())
        except:
            print("unable to read config file")
        if self.__config:
            self.__loop = asyncio.get_event_loop()
            future = asyncio.Future()
            asyncio.ensure_future(self._get_server(future))
            self.__loop.run_until_complete(future)
            self.__server = future.result()            
    
    def stop(self):
        print ("STOP")
        print(self.__server)
        self.__server.close()

s = Myserver()
s.start("config.yml")
s.stop()
Ich instanziere Myserver und rufe start auf und es wird ein loop gestartet der die _get_server Funktion abarbeitet und mittels Future bekomme ich das Server object. Nur wenn ich danach die stop methode ausführe, habe ich zwar das Objekt noch, nur wird die Verbindung nicht beendet. Ich vermute, dass es daran liegt, dass es verschiedene Event loops sind, einmal mein main und eben der asyncio loop. Somit müsste der Asyncio event loop eigentlich im Konstruktor erzeugt und bei Dereferenzierung geschlossen werden. Dazwischen findet dann die "Action" mit dem Server statt. Nur, wie kann ich weitere Methoden (wie z.B. stop) der Klasse aufrufen währenddessen der Asyncio event loop mit run_forever läuft. Gibt es dafür eine Lösung? Bzw. eine Lösung für das grundsätzliche Problem einen Eventloop in einem Objekt zu haben und aus der Main trotzdem noch Methoden aufzurufen??
Sirius3
User
Beiträge: 18226
Registriert: Sonntag 21. Oktober 2012, 17:20

@BugsBunny74: so funktioniert nebenläufige Programmierung nicht. Man hat entweder einen Server, der Ereignisse abarbeitet, oder ein lineares Programm, das nach und nach Funktionen ausführt. Beides zu mischen macht keinen Sinn, weil man dann eigentlich zwei unabhängige Programme haben möchte.
Bei Deinem Beispiel stellt sich für mich die Frage, wann stop aufgerufen wird. Entweder nie (klassischer Server) oder weil ein Ereignis eintritt, das sagt, "jetzt bin ich mit meiner Arbeit fertig". Und dieses Ereignis wird dann innerhalb des Event-Loops abgearbeitet.
Benutzeravatar
pillmuncher
User
Beiträge: 1527
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@BugsBunny74: Anm.: die zwei führenden Unterstriche bedeuten nicht, was du glaubst, das sie bedeuten. Sie führen zu sog. Private Name Mangling, was vielleicht ein unglücklicher Ausdruck ist, weil es der Funktionalität nach nichts mit dem private aus C++/C#/Java/... zu tun hat. Private Name Mangling dient ausschließlich dazu, Namenskonflikte bei Mehrfachvererbung zu vermeiden. Das, was du statt dessen möchtest, sind Namen mit einem einzigen führenden Unterstrich. Dieser bedeutet: der so beginnende Name ist ein Implementierungsdetail, Verwendung auf eigene Gefahr. Wirkliche Privatheit im Sinne der o.g. Sprachen gibt es in Python überhaupt nicht. Wozu auch?
In specifications, Murphy's Law supersedes Ohm's.
BugsBunny74
User
Beiträge: 8
Registriert: Montag 8. Februar 2016, 13:14

Hallo zusammen,

vielen Dank für eure Antworten!

@pillmuncher
Diese Unterstrcihe sind für mich Kennzeichnungen innerhalb der Klasse. Hab über die Jahre da mein eigenes System entwickelt mit ein oder zwei Unterstrichen, je nachdem was es ist. Trotzdem danke!

@sirius
Beides mischen macht keinen Sinn, kann ich nur zustimmen! Und ist in meinem Fall auch technisch unmöglich. Kurz die Hintergründe, es geht um ein Projekt in dem ein Partner einen Kommunikationsstack in Python3 geschrieben hat und eine API bereitstellt. Diese API bietet jedoch keine Methoden oder funktionen sondern Coroutinen, ergo Generatoren. Diese machen zwar was sie sollen, sind aber in sich geschlossen und laufen autark ab. Die Routinen unter sich kann man beliebig kombinieren, je nachdem was man möchte, nur hilft das alles nicht wenn man eine solche Architektur mit einer Java Klasse nutzen möchte. Diese Java klasse kommuniziert mit JNI mit einer DLL die das Python script embedded. Der Umstand Java mit Python auf diese Art verheiraten zu wollen ist schon etwas schwierig, aber wenn dazu noch 2 völlig verschiedene Architekturen aufeinander prallen wird es unmöglich. Ich bin erst Anfang der Woche das erste mal mit diesen Python3 "Features" in Berührung gekommen, aber du scheinst dich da auszukennen. Deswegen an dich mal die Frage, wenn ich einen Kommunikationsstack mit einer "Generatoren API" habe, ist der Aufwand stattdessen Funktionen oder Methoden bereitzustellen gleichzusetzen mit einer Neuentwicklung oder wäre es "nur" eine Modifizierung des Codes?

Grüsse,
Bugs
Sirius3
User
Beiträge: 18226
Registriert: Sonntag 21. Oktober 2012, 17:20

@BugsBunny74: am saubersten wäre es, in Java einen Service zu schreiben, der die API der Klasse bereit stellt und mit diesem mit Python über ein asyncio-kompatibles Interface zu kommunizieren.
Antworten