【FastAPI】backgroundtasksが同期的に実行される場合

【FastAPI】backgroundtasksが同期的に実行される場合

環境

同期的に実行される(非同期実行にならなかった)コード

以下のような感じでFastAPIのbackgroundtaskを活用して、非同期に処理を行おうとしていました。
(実際の処理からかなり簡略化しています。)

しかし、実際にこのエンドポイントを呼び出すと結果が返ってくるのは9秒後となりました。

これはbackground_tasks.add_taskで追加したタスクがすべて完了したタイミングなので、上手く非同期実行が出来ていませんでした。

ちょっと込み入ったことをしていたので、バグなのか仕様なのか実装ミスなのか分からなかったので調査しました。

良くある想定通り非同期実行されるコード

「do_async」関数の内容を変えることで、通常通り非同期に実行されるようになります。

①非同期になる例-1

②非同期になる例-2

どうやらasyncを付けた関数の中でasyncっぽいことをしないのが原因なのでしょうかねえ…。

非同期にならない理由(推測)

FastAPIは裏側でstarletteというASGIサーバーを使っていて、backgrouondtaskはこのstarletteの機能をふんだんに使ったものとなっています。

なのでこちらを覗いてみてあげましょう。
そうすると、以下のようにasync/syncによって挙動が変わっています。

通常の関数の場合はthredpoolに入れるので明示的に非同期になるようです。
ただ、asyncの場合はただ実行するだけ。ううむ… なんでこれで非同期になるんだ…。

と、いうことでasyncの場合についてもう少し詳しく調査してみます。

③即座にレスポンスが返ってくる例

④3秒後にレスポンスが返ってくる例

眺めていると何となく条件が見えてきます。

  • backgrouondtaskと通所の処理が全て完了(っぽい状態)になったらレスポンスを返却する仕組み
  • awaitが実行されることをFastAPI(starlette)が監視
  • awaitが実行されたタイミングでbackgrouondtaskのステータスを「仮完了」のようなものに変更

という流れなのかなあと推測できます。中々情報が見つからずに厄介でしたがおおよそこんな所でしょう…。

解決策

可能であればFastAPIやStarlette側の対応に期待したところですが、そうも言ってられない方たちも多いかと思います。

そのような場合はこの例のように、

backgrouondtaskに登録する処理の最初で

のような処理を追加することが簡易的な解決策となります。

バグなのか仕様なのかは分かりませんが、とにかく分かりずらいですね。。

参考

https://github.com/tiangolo/fastapi/issues/1313

https://github.com/encode/starlette/issues/436

https://github.com/encode/starlette/issues/919

FastAPIカテゴリの最新記事