web-dev-qa-db-ja.com

FlaskルートからのPython3 Asyncio呼び出し

flaskルートが実行されるたびに非同期関数を実行したい。現在、私のabar関数は実行されません。私に理由を教えてくれる?どうもありがとうございました:

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()

また、1つのブロッキング呼び出しを別のスレッドに入れようとしました。しかし、まだ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

一部の非同期機能をFlaskアプリに完全に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)

これにより、非同期関数が戻るまでFlask応答がブロックされますが、それでもいくつかの巧妙なことができます。このパターンを使用して、 aiohttp を使用して多数の外部リクエストを並行して実行し、それらが完了すると、データ処理とテンプレートレンダリングの従来のflaskに戻ります。

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

あなたの問題に対するより簡単な解決策は(私の偏見では)Flaskから Quart に切り替えることです。その場合、スニペットは単純化され、

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)

他の回答で述べたように、Flaskアプリの実行はブロックされており、asyncioループと相互作用しません。一方、Quartはasyncio上に構築されたFlask AP​​Iであるため、期待どおりに機能するはずです。

また、更新として、Flask-Aiohttpは maintained ではなくなりました。

12
pgjones

同じ理由で、この印刷物は表示されません。

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

@dirnが既に述べたようにapp.runもブロックしているため、loop.run_forever()は呼び出されません。

グローバルブロッキングイベントループの実行-asyncioコルーチンおよびタスクを実行できる唯一の方法ですが、ブロッキングFlaskアプリ(または他の一般的なもの)の実行とは互換性がありません。

非同期Webフレームワークを使用する場合は、非同期に作成されたものを選択する必要があります。たとえば、おそらく現在最も人気のあるのは 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

更新:

バックグラウンドスレッドでイベントループを実行しようとしています。私はあまり調査しませんでしたが、トレッドの安全性に何らかの問題があるようです。多くのasyncioオブジェクトはスレッドセーフではありません。この方法でコードを変更すると、動作します:

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

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

しかし、これも非常に悪い考えです。非常に不便なだけでなく、asyncioを起動するためにスレッドを使用する場合、なぜasyncioの代わりに Flaskでスレッドを使用する しないのでしょうか?必要なFlaskと並列化があります。

それでも納得できなかったら、少なくとも Flask-aiohttp プロジェクトを見てください。 Flask apiに近いので、あなたがやろうとしていることよりもさらに良いと思います。

4

動作していなかった上記の動作コードを提供してくれたJL Diaz(RealPythonから)に感謝します。

ここで何かを変更する必要がある場合は、お気軽にコメントしてください。

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