"async await" funktioniert nicht asynchron

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
Sempervivum
User
Beiträge: 10
Registriert: Montag 22. November 2021, 21:57

Liebe Python-Programmierer,
ich bin relativ neu in Python und wollte mich ein wenig mit "async await" vertraut machen. Ich bin mehr in Javascript zu Hause, dort gibt es das auch und ich kann ganz gut damit umgehen.
Die Aufgabe ist folgende: Es gibt eine ganze Reihe von API-Abfragen, sagen wir 9. In Wirklichkeit viel mehr, daher wollte ich sie zum Teil asynchron abfragen, also z. B. jeweils 3 synchron und diese Blöcke dann asynchron. Damit die Wartezeit nicht zu lang wird. Zusätzlich gibt es zu jeder Abfrage noch eine ID, die wieder zur Verfügung stehen muss, wenn die Antwort vom Server da ist.

Den Server habe ich mit einem PHP-Skript simuliert:

Code: Alles auswählen

<?php
sleep(1);
echo '{"param1":' . $_POST['param1'] . '}'
?>
Leider bin ich dabei mit "async await" nicht zum Ziel gekommen. Erster Versuch:

Code: Alles auswählen

    import asyncio
    import sys
    import requests

    params = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    ids = [11, 12, 13, 14, 15, 16, 17, 18, 19]
    url = r'http://localhost//_python/async-await/the-api.php'

    // Get 3 requests synchronously:
    async def getThem(url, params2, ids2):
        idx2 = 0
        for param in params2:
            response = requests.post(url, data={'param1': param})
            print(response.json(), ids2[idx2])
            idx2 += 1

    idx = 0
    params2 = []
    ids2 = []
    // Prepare blocks of 3 params and IDs each
    // and get data from API
    for param in params:
        params2.append(param)
        ids2.append(ids[idx])
        idx += 1
        if (idx % 3) == 0:
            print(idx, params2)
            asyncio.run(getThem(url, params2, ids2))
            params2 = []
            ids2 = []
Die Abfragen werden präzise abgewickelt aber eine nach der anderen, nichts asynchron.
Ich habe dann noch viel nachgelesen, vor allem bei Stackoverflow und Versuche mit gather unternommen aber bin nicht zum Ziel gekommen, immer wurden die Abfrage nacheinander abgewickelt.

Erst als ich versucht habe, für jede Abfrage einen Thread zu starten, hat es funktioniert:

Code: Alles auswählen

    def getThem(url, params2, ids2):
        idx2 = 0
        for param in params2:
            response = requests.post(url, data={'param1': param})
            print(str(response.json())+' ' + str(ids2[idx2]))
            idx2 += 1

    idx = 0
    params2 = []
    ids2 = []
    for param in params:
        params2.append(param)
        ids2.append(ids[idx])
        idx += 1
        if (idx % 3) == 0:
            print(idx, params2)
            download_thread = threading.Thread(
                target=getThem, name="Downloader", args=[url, params2, ids2])
            download_thread.start()
            params2 = []
            ids2 = []
Aber irgend wie muss es doch auch mit "async await" gehen. Möglicher Weise bin ich da von JS her betriebsblind und erkenne die Systematik in Python nicht.

Beste Grüße - Ulrich
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Na du musst schon *jede* potentiell blockierende Anweisung durch eine ersetzen, welche die Kontrolle an den scheduler zurueckgibt. Einfach async vor eine Funktion schreiben reicht da nicht. Das ist auch in JS nicht anders, da musst du ja auch zB

Code: Alles auswählen

const response = await superagent.get('https://api.nasa.gov/planetary/apod').query(queryArguments)
machen. Statt einfach einen synchronen, blockierenden Aufruf zu nutzen. requests scheint das nicht zu koennen, darum gibt es zB aiohttp:

https://docs.aiohttp.org/en/stable/
Sempervivum
User
Beiträge: 10
Registriert: Montag 22. November 2021, 21:57

Hallo deets, danke für die schnelle Antwort.

In Javascript benutze ich fetch und das arbeitet grundsätzlich asynchron ohne dass ich etwas dazu tun muss. Probleme gibt es nur in der Richtung, dass häufig synchrones Verhalten erwartet wird und das nicht möglich ist.

Die drei Anfragen in der Funktion getThem sollen ausdrücklich nacheinander synchron abgearbeitet werden, damit der Server nicht mit quasi-gleichzeitigen Anfragen zu geschüttet wird. Nur die Aufrufe von getThem sollen quasi-gleichzeitig ausgeführt werden. D. h. es soll genau so funktionieren wie bei dem Skript mit dem Starten von Threads.

Beste Grüße - Ulrich
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ja und? Das steht doch in keinem Widerspruch zu dem, was ich gesagt habe. Deine getThem-Funktion arbeitet jeweils die ihr ueberantworteten Requests nacheinander und nicht parallel ab. Aber das aendert nichts daran, dass sie ein Request mit einer echt asynchronen Bibliothek machen muss, um waehrend der laufenden Anfrage den *anderen* getThems die Moegklichkeit zu geben, ihre Arbeit (und ihr warten) zu erledigen.

Und so arbeitet man auch in Javascript. Asynchrone Programmierung erfordert explizites benutzen von await. Wenn man das nicht macht, dann blockiert der Code auch alles andere. Auch bei fetch ist das notwendig, wenn man einen wie hier gezeigten linearen Programmablauf will, siehe https://developer.mozilla.org/en-US/doc ... sing_Fetch

Wenn du natuerlich then benutzt, dann verkettest du einfach callbacks - das ist syntaktisch anders, aber effektiv auch wieder das abgeben der Kontrolle. Denn dann laeuft ja der Code *nach* dem fetch (nicht in den thens!) auch weiter, bis die registrierten Callbacks dank der beendeten Netzwerkanfragen aufgerufen werden.
Sempervivum
User
Beiträge: 10
Registriert: Montag 22. November 2021, 21:57

das aendert nichts daran, dass sie ein Request mit einer echt asynchronen Bibliothek machen muss, um waehrend der laufenden Anfrage den *anderen* getThems die Moegklichkeit zu geben, ihre Arbeit (und ihr warten) zu erledigen.
Mir scheint, das ist des Rätsels Lösung: Obwohl bei verschiedenen Funktionsaufrufen aktiviert "wissen" anscheinend die Requests von einander und lassen es nicht zu, dass ein anderer aktiv wird, bevor der erste abgearbeitet ist. Verstehe ich das jetzt richtig?

Irgend wie verhalten sich da JS und Python genau entgegen gesetzt: Bei JS ist alles asynchron (wenn man mal von einer Option bei XMLHttpRequest absieht) und man kann nur mit "async await" erreichen, dass Anfragen synchron abgearbeitet werden. Bei Python dagegen ist erst Mal alles synchron und es erfordert zusätzliche Maßnahmen, "async await", zunächst Mal ähnlich wie in JS, aber funktioniert nicht mit jeder Bibliothek.
Sirius3
User
Beiträge: 18279
Registriert: Sonntag 21. Oktober 2012, 17:20

Es ist genau anders herum, asynchrone Funktionen müssen voneinander "wissen", in dem sie die Kontrolle an den zentralen Dispatcher übergeben, wenn sie auf etwas warten.
Und da verhalten sich Javascript und Python identisch. Normaler Funktionen werden synchron abgearbeitet, und asynchrone Funktionen müssen speziell dafür geschrieben sein.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du hast das leider immer noch nicht verstanden. Python und JavaScript Verhalten sich im Rahmen dieser Diskussion gleich - sie arbeiten erstmal synchron ihre Befehle ab. Nur hat man damit bei JS ein problem: Wenn man also Dinge machen will, die Lange dauern (HTTP requests zb), dann da friert der browser ein. Der hat nur einen thread. Früher hat man das dann mit callbacks erledigt: das XMLHttpRequest wird erzeugt, bekommt callbacks für Erfolg oder Fehlerfall gesetzt, und der code läuft weiter. Wenn dann der browser im Hintergrund das request erledigt hat, kramt er dann einfach den callback hervor, und ruft den auf. Und der registriert im Zweifel wieder callbacks, und so weiter & so fort. Und schon ist man in „callback hell“. Mit tief verschachteltem Code, und schwer verfolgbarer Logik.

Das fetch + then ist eine minimale Verbesserung des ganzen, indem es eine Verkettung von Promises ermöglicht. Die sind aber immer noch asynchron. Nach den verketteten thens kann code stehen, der sofort ausgeführt wird. Nicht erst, wenn das promise fertig ist!

Eine andere Idee sind dann eben async/await. Da wird statt eines callbacks mit await signalisiert, dass der Code an dieser Stelle die Kontrolle abgibt, bis das asynchrone Promise, das da erzeugt wurde, fertig ist. Dann gehts weiter. Das ist halt schöner zu lesen, weil es synchron aussieht. Aber es ist asynchron, weil der Code andauernd unterbrochen wird, bis die promises eingelöst sind.

Und genauso macht asyncio das in Python auch.
Sempervivum
User
Beiträge: 10
Registriert: Montag 22. November 2021, 21:57

Wenn ich schrieb "Bei JS ist alles asynchron", so meinte ich das nur auf HTTP-Requests bezogen, die ja hier das Problem sind.
Die Sache mit den Callbacks bei XMLHttpRequest und then bei fetch war mir schon bekannt.
Über den Rest muss ich noch weiter nachdenken.
Sempervivum
User
Beiträge: 10
Registriert: Montag 22. November 2021, 21:57

... nach kurzem Nachdenken noch eine Frage: Der Code in meinem ersten Posting funktioniert nicht asynchron weil ich den Request mit await starten müsste, richtig? Und wenn ich den Modul "requests" verwende, habe ich das Problem, dass dieser das "await" nicht unterstützt?
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

So ist es.
Benutzeravatar
__blackjack__
User
Beiträge: 14078
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wenn man die `requests`-API mag oder gewohnt ist, könnte sich vielleicht ein Blick auf das `httpx`-Modul lohnen. Die API ist eng an `requests` angelehnt und es hat auch eine ``async``-API.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Antworten