1. ホーム
  2. python

[解決済み] Flaskがレスポンスを返した後に関数を実行する

2023-07-10 02:28:01

質問

以下のコードを実行する必要があります。 の後に を実行する必要があります。そのためにCeleryのようなタスクキューをセットアップするほど複雑だとは思いません。重要な要件は、Flaskはこの関数を実行する前にクライアントにレスポンスを返さなければならないということです。関数が実行されるのを待つことはできないのです。

これに関するいくつかの既存の質問がありますが、どの回答も、レスポンスがクライアントに送信された後にタスクを実行することに対処していないようです、それらはまだ同期的に実行され、その後レスポンスが返されます。

どのように解決するのですか?

長い話になりますが、Flaskはこれを実現するための特別な機能を提供していません。単純な一回限りのタスクであれば、以下のようなPythonのマルチスレッドを検討してください。より複雑な構成の場合は、RQやCeleryのようなタスクキューを使用します。

なぜですか?

Flaskが提供する関数とその理由を理解することは重要です。 をしないのか? が意図した目標を達成できないのかを理解することが重要です。これらはすべて他のケースでは有用であり、良い読み物ですが、バックグラウンドタスクでは役に立ちません。

Flask の after_request ハンドラ

Flaskの after_request ハンドラで、詳しくは この遅延リクエストコールバックのパターンは このスニペットは、リクエストごとに異なる関数をアタッチするためのものです。 のようにすると、コールバック関数にリクエストが渡されます。意図する使用例としては リクエストを変更する 例えば、クッキーを添付するような場合です。

このように、リクエストはこれらのハンドラが実行を終えるまで待機します。なぜなら、結果的にリクエスト自体が変更されることを期待しているからです。

Flaskの teardown_request ハンドラ

これは after_request と似ていますが teardown_request は受信しません。 request オブジェクトを受け取りません。ということは、リクエストを待たないということですよね?

これは解決策のようで、次のように Stack Overflow の同様の質問に対するこの回答 が示唆しています。そして、Flaskのドキュメントには、次のように説明されています。 のコールバックは実際のリクエストから独立しています。 であり、リクエストコンテキストを受け取らないとFlaskのドキュメントで説明されているので、これを信じる十分な理由があるでしょう。

残念ながら teardown_request はまだ同期ですが、これは Flask のリクエスト処理の後半で、リクエストがもはや変更不可能になったときに起こるだけです。Flask は <は は依然としてティアダウン関数 が完了するのを待ってからレスポンスを返します。 このFlaskのコールバックとエラーのリスト が指示します。

Flaskのストリーミングレスポンス

Flask はジェネレータを渡すことでレスポンスをストリームすることができます。 Response() というように このStack Overflowの回答は、同様の質問に対して が示唆します。

ストリーミングでは、クライアントが が行います。 はリクエストが終了する前にレスポンスの受信を開始します。しかし、リクエストはまだ同期的に実行されるので、 リクエストを扱うワーカーはストリームが終了するまでビジー状態です。

このストリーミングのためのFlaskパターン を使用するためのドキュメントが含まれています。 stream_with_context() を使うことについてのドキュメントがあります。これはリクエストコンテキストを含めるために必要です。

では、解決策は?

Flaskはバックグラウンドで関数を実行するソリューションを提供していません。

ほとんどの場合、この問題を解決する最善の方法は、RQやCeleryのようなタスクキューを使うことです。これはこの種の質問に対する最も一般的な回答です。なぜなら、それが最も正しく、コンテキストなどを正しく考慮する方法で物事を設定するよう強制されるからです。

バックグラウンドで関数を実行する必要があり、これを管理するためにキューをセットアップしたくない場合、Pythonのビルトインである threading または multiprocessing でバックグラウンドワーカーを起動します。

にはアクセスできません。 request などの Flask のスレッドロカールにバックグラウンドタスクからアクセスすることはできません。代わりに、ビューを作成するときに、ビューから必要なデータをバックグラウンドスレッドに渡します。

@app.route('/start_task')
def start_task():
    def do_work(value):
        # do something that takes a long time
        import time
        time.sleep(value)

    thread = Thread(target=do_work, kwargs={'value': request.args.get('value', 20)})
    thread.start()
    return 'started'