web-dev-qa-db-fra.com

Appel Python3 Asyncio de Flask route

Je veux exécuter une fonction asynchrone à chaque fois que la route flask est exécutée. Actuellement, ma fonction abar n'est jamais exécutée. Pouvez-vous me dire pourquoi? Merci beaucoup:

import asyncio
from flask import Flask

async def abar(a):
    print(a)

loop = asyncio.get_event_loop()
app = Flask(__name__)

@app.route("/")
def notify():
    asyncio.ensure_future(abar("abar"), loop=loop)
    return "OK"

if __name__ == "__main__":
    app.run(debug=False, use_reloader=False)
    loop.run_forever()

Je l'ai essayé aussi pour mettre l'appel bloquant dans un fil séparé. Mais il n’appelle toujours pas la fonction abar.

import asyncio
from threading import Thread
from flask import Flask

async def abar(a):
    print(a)

app = Flask(__name__)

def start_worker(loop):
    asyncio.set_event_loop(loop)
    try:
        loop.run_forever()
    finally:
        loop.close()

worker_loop = asyncio.new_event_loop()
worker = Thread(target=start_worker, args=(worker_loop,))

@app.route("/")
def notify():
    asyncio.ensure_future(abar("abar"), loop=worker_loop)
    return "OK"

if __name__ == "__main__":
    worker.start()
    app.run(debug=False, use_reloader=False)
25
user24502

Vous pouvez intégrer certaines fonctionnalités asynchrones à des applications Flask) sans avoir à les convertir complètement en version asyncio.

import asyncio
from flask import Flask

async def abar(a):
    print(a)

loop = asyncio.get_event_loop()
app = Flask(__name__)

@app.route("/")
def notify():
    loop.run_until_complete(abar("abar"))
    return "OK"

if __name__ == "__main__":
    app.run(debug=False, use_reloader=False)

Cela bloquera la réponse Flask jusqu'au retour de la fonction asynchrone, mais cela vous permet néanmoins de faire des choses intelligentes. J'ai utilisé ce modèle pour effectuer de nombreuses requêtes externes en parallèle à l'aide de aiohttp , et quand ils sont terminés, je suis de retour dans la méthode traditionnelle flask) pour le traitement des données et le rendu des modèles.

import aiohttp
import asyncio
import async_timeout
from flask import Flask

loop = asyncio.get_event_loop()
app = Flask(__name__)

async def fetch(url):
    async with aiohttp.ClientSession() as session, async_timeout.timeout(10):
        async with session.get(url) as response:
            return await response.text()

def fight(responses):
    return "Why can't we all just get along?"

@app.route("/")
def index():
    # perform multiple async requests concurrently
    responses = loop.run_until_complete(asyncio.gather(
        fetch("https://google.com/"),
        fetch("https://bing.com/"),
        fetch("https://duckduckgo.com"),
        fetch("http://www.dogpile.com"),
    ))

    # do something with the results
    return fight(responses)

if __name__ == "__main__":
    app.run(debug=False, use_reloader=False)
18
Travis Terry

Une solution plus simple à votre problème (de mon point de vue biaisé) consiste à passer à Quart à partir de Flask. Si c'est le cas, votre extrait se simplifie,

import asyncio
from quart import Quart

async def abar(a):
    print(a)

app = Quart(__name__)

@app.route("/")
async def notify():
    await abar("abar")
    return "OK"

if __name__ == "__main__":
    app.run(debug=False)

Comme indiqué dans les autres réponses, l'exécution de l'application Flask est bloquante et n'interagit pas avec une boucle asyncio. Quart est en revanche l'API construite Flask construite sur asyncio, cela devrait donc fonctionner comme vous le souhaitez.

Également comme mise à jour, Flask-Aiohttp n'est plus mainten .

12
pgjones

Pour la même raison, vous ne verrez pas cette impression:

if __name__ == "__main__":
    app.run(debug=False, use_reloader=False)
    print('Hey!')
    loop.run_forever()

loop.run_forever() n'est jamais appelé car, comme @dirn l'a déjà noté, app.run bloque également.

Exécuter une boucle d’événement de blocage global - c’est le seul moyen d’exécuter asyncio des tâches et routines, mais ce n’est pas compatible avec le blocage Flask app (ou toute autre chose de ce genre en général) .

Si vous souhaitez utiliser une infrastructure Web asynchrone, vous devez en choisir une qui soit créée pour être asynchrone. Par exemple, le plus populaire est probablement aiohttp :

from aiohttp import web


async def hello(request):
    return web.Response(text="Hello, world")


if __name__ == "__main__":
    app = web.Application()
    app.router.add_get('/', hello)
    web.run_app(app)  # this runs asyncio event loop inside

Upd:

A propos de votre tentative d'exécuter la boucle d'événement dans le fil de l'arrière-plan. Je n'ai pas beaucoup étudié, mais il semble y avoir un problème lié à la sécurité de la bande de roulement: de nombreux objets asyncologiques ne sont pas thread-safe. Si vous modifiez votre code de cette façon, cela fonctionnera:

def _create_task():
    asyncio.ensure_future(abar("abar"), loop=worker_loop)

@app.route("/")
def notify():
    worker_loop.call_soon_threadsafe(_create_task)
    return "OK"

Mais encore une fois, c'est une très mauvaise idée. Ce n'est pas seulement très gênant, mais je suppose que cela n'aurait pas beaucoup de sens: si vous allez utiliser thread pour démarrer asyncio, pourquoi ne pas tilisez simplement des threads dans Flask au lieu de asyncio? Vous aurez Flask vous voulez et la parallélisation.

Si je ne vous ai toujours pas convaincu, regardez au moins le projet Flask-aiohttp . Il a près de Flask api et je pense encore mieux que ce que vous essayez de faire.

4
Mikhail Gerasimov

Merci à JL Diaz (de RealPython) d’avoir fourni un code de travail pour ce qui précède qui ne fonctionnait pas.

Si quelque chose ici devait être changé, n'hésitez pas à commenter.

import aiohttp
import asyncio
import async_timeout
from quart import Quart, jsonify

app = Quart(__name__)

async def fetch(url):
    async with aiohttp.ClientSession() as session, async_timeout.timeout(10):
        async with session.get(url) as response:
            return await response.text()

def fight(responses):
    return jsonify([len(r) for r in responses])

@app.route("/")
async def index():
    # perform multiple async requests concurrently
    responses = await asyncio.gather(
        fetch("https://google.com/"),
        fetch("https://bing.com/"),
        fetch("https://duckduckgo.com"),
        fetch("http://www.dogpile.com"),
    )

    # do something with the results
    return fight(responses)

if __name__ == "__main__":
    app.run(debug=False, use_reloader=False)
0
Tomer