Python3 asyncio appel de Flask route

je veux exécuter une fonction async chaque fois que la route du flacon 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()

j'ai essayé aussi de mettre un appel bloquant dans un sujet 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)
12
demandé sur user24502 2017-12-16 05:23:29

3 réponses

vous pouvez intégrer certaines fonctionnalités async dans les applications Flask sans avoir à les convertir complètement en 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 de la fiole jusqu'à ce que la fonction async revienne, mais cela vous permet tout de même de faire des choses intelligentes. J'ai utilisé ce modèle pour effectuer de nombreuses requêtes externes en parallèle en utilisant aiohttp, et puis quand ils sont terminés, je suis de nouveau dans le flacon traditionnel pour le traitement des données et le rendu des gabarits.

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)
9
répondu Travis Terry 2018-05-04 14:49:40

une solution plus simple à votre problème (dans ma vue biaisée) est de passer à Pinte de la fiole. Si donc, votre extrait de code 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'application Flask run bloque et n'interagit pas avec une boucle asyncio. D'un autre côté, Quart est l'API Flask construite sur asyncio, il devrait donc fonctionner comme vous l'attendez.

aussi comme mise à jour, Flask-Aiohttp n'est plus maintenus.

5
répondu pgjones 2018-01-28 21:34:05

Pour la même raison que 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.

en cours d'Exécution global de blocage de la boucle - est la seule façon vous pouvez exécuter asyncio coroutines et tâches, mais il n'est pas compatible avec le fonctionnement de L'application Flasque de blocage (ou avec toute autre chose du genre en général).

si vous voulez utiliser un framework web asynchrone, vous devez choisir un framework créé pour être asynchrone. Exemple, probablement le plus populaire est maintenant 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:

au sujet de votre essai d'exécuter la boucle d'événement dans le fil d'arrière-plan. Je n'ai pas beaucoup enquêté, mais il semble que le problème soit lié à la sécurité de la bande de roulement: beaucoup d'objets asyncio ne sont pas thread-safe. Si vous changez votre code de cette façon, il 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 ça n'aurait pas beaucoup de sens: pour utiliser le thread pour démarrer asyncio, pourquoi ne pas il suffit d'utiliser les fils dans la fiole au lieu d'asyncio? Vous aurez flasque que vous voulez et la parallélisation.

Si je n'ai pas à vous convaincre, au moins prendre un coup d'oeil à fiole-aiohttp projet. Il a proche de Flask api et je pense encore mieux que ce que vous essayez de faire.

4
répondu Mikhail Gerasimov 2017-12-16 12:02:55