Error [There is no current event loop in thread 'MainThread']

`RuntimeError: There is no current event loop in thread 'MainThread'` の直し方(Python 3.14 の asyncio)

FIX SUMMARY verified
Applies when
python@sha256:b877e50bd90de10af8d82c57a022fc2e0dc731c5320d762a27986facfc3355c1asyncio.get_event_loop() called outside a running loop; the main-thread-only 'create one implicitly' special case warned in 3.12/3.13 and is gone in 3.14 (measured 3.11-3.14)

Verified: reproduced in python@sha256:b877e50bd90de10af8d82c57a022fc2e0dc731c5320d762a27986facfc3355c1, then the There is no current event loop in thread 'MainThread' signature was gone after the fix (exit 0).

asyncio.get_event_loop() は、10 年以上「event loop が無ければ黙って作る」関数でした。Python 3.14 は、そのふるまいをやめました。

RuntimeError: There is no current event loop in thread 'MainThread'.

落ちているのはたいてい、次の形です。

import asyncio

loop = asyncio.get_event_loop()      # ここで RuntimeError
loop.run_until_complete(main())

3.13 では同じコードが警告つきで動いていました。3.11 では警告すら出ませんでした。

Python 3.14 は、asyncio.get_event_loop() が「event loop が無ければ暗黙に作る」ふるまいをやめました。 最小の修正は、入口を asyncio.run() に置き換え、loop が要る場所では asyncio.get_running_loop() を使うことです。

なぜ Python 3.14 で出るのか:黙って作る → 警告つきで作る → 作らない

get_event_loop() を、実行中の loop が無い状態のメインスレッドで呼ぶと、版ごとにこうなります。

Pythonasyncio.get_event_loop()(loop が無いメインスレッド)
3.11loop を作って返す(警告なし)
3.12DeprecationWarning: There is no current event loop を出して、loop を作って返す(終了コード 0)
3.13同上(終了コード 0)
3.14RuntimeError: There is no current event loop in thread 'MainThread'.(終了コード 1)

3.12・3.13 で出ていた DeprecationWarning が見えていたかどうかは、その呼び出しがどのモジュールにあったかで決まります。CPython の既定の警告フィルタは、DeprecationWarning発生元が __main__ のときだけ表示し、それ以外のモジュールでは抑制するからです(既定の警告フィルタ)。

default::DeprecationWarning:__main__
ignore::DeprecationWarning
...

つまり、app.py に直接書いた get_event_loop() なら、-W なしでも 3.13 は警告を出していました(実測)。ところがアプリのコードはたいていパッケージ側のモジュールにあるので、そこで呼んでいる分は既定では 1 行も出ません(実測)。予告が届かなかったのは、そこです。

3.13 のうちに洗い出すなら、抑制を外して実行します。

python -W always::DeprecationWarning app.py
python -W always::DeprecationWarning -m pytest

ただし、これを 1 回流しても、落ちる行が全部出るわけではありません。3.13 の get_event_loop() は、警告を出したあと loop を作って current loop に設定します。だから同じスレッドの 2 回目以降は「current loop がある」状態になり、警告を出しません(実測)。1 回の実行で見えるのは、最初の 1 箇所だけです。警告が出た箇所を直して再実行する、を繰り返してください。合わせて get_event_loop( を静的に検索しておくと、取りこぼしが減ります。

このエラー文は 3.14 で生まれたものではない

There is no current event loop in thread ... を検索すると、Python 3.14 より前の記事が大量に出てきます。文言が同じだからです。このエラーは昔から、メインスレッド以外では出ていました。

def worker():
    loop = asyncio.get_event_loop()   # threading.Thread の中

# 3.11 / 3.12 / 3.13 / 3.14 すべて
# RuntimeError: There is no current event loop in thread 'Thread-1 (worker)'.

get_event_loop() は「今のスレッドに紐づいた loop」を返す関数で、無ければエラーにするのが本来の動作です。そのうえで、メインスレッドにだけ「無ければ暗黙に作る」という特例がありました。3.14 が消したのはこの特例です。エラー文のスレッド名が 'MainThread' になっているのは、そのためです。

この見方をすると、直し方も決まります。暗黙に作られていた loop を、自分で用意するか、loop を自分で持たない書き方に移るかです。

直し方:asyncio.run() を入口にする

推奨は後者です。loop の生成・実行・後片付けを asyncio.run() に任せます。

import asyncio

async def main():
    loop = asyncio.get_running_loop()     # loop が要るならここで取る
    print(type(loop).__name__)

asyncio.run(main())

asyncio.run() は loop を作り、コルーチンを実行し、終わったら閉じます。loop オブジェクトが必要な場面(loop.run_in_executor()loop.call_later() など)では、async 関数の中から asyncio.get_running_loop() を呼びます。こちらは「実行中の loop」を返す関数なので、暗黙生成の特例とは無関係です。

なお、async 関数の中で get_event_loop() を呼んでいるコードは 3.14 でもそのまま動きます。実行中の loop があれば、get_event_loop() はそれを返すからです(実測:3.11〜3.14 のすべてで get_event_loop() is get_running_loop()True)。「3.14 で get_event_loop() が使えなくなった」わけではありません。落ちるのは、実行中の loop が無く、set_event_loop() もされていないときだけです。ライブラリの内部が get_event_loop() を呼んでいても、それが async の文脈からなら手を入れる必要はありません。

run_until_complete の構造を残したいとき

asyncio.run() への書き換えが大きすぎる場合、暗黙に作られていた loop を明示的に作るだけでも通ります。

import asyncio

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
    loop.run_until_complete(main())
finally:
    loop.close()

3.14 でも new_event_loop()set_event_loop()run_until_complete() はすべて動きます(実測)。消えたのは「無ければ暗黙に作る」ふるまいだけで、loop を作る手段そのものは残っています。既存のコードが loop オブジェクトを持ち回っている(複数の run_until_complete を順に呼ぶ、シグナルハンドラを loop.add_signal_handler() で付けている、といった)構造なら、この 2 行を足すのが最短です。

ただしこの形では、後片付けを自分で書くことになります。asyncio.run() は保留中のタスクのキャンセルと loop の close までを引き受けるので、新しく書くなら asyncio.run() を選び、上の形は既存構造を保つための足場に留めてください。

その次に来るもの:event loop policy

3.14 では、event loop policy の API にも予告が出ています。

DeprecationWarning: 'asyncio.get_event_loop_policy' is deprecated and slated for removal in Python 3.16

3.13 では無言だったものが、3.14 で「非推奨・3.16 で削除」と名指しされました。これは get_event_loop() が 3.12・3.13 で置かれていたのと同じ段です。Windows で WindowsProactorEventLoopPolicy に差し替える、テストで policy を入れ替える、といったコードを持っているなら、3.16 で同じ移行を踏むことになります。いま -W always::DeprecationWarning を通しておくと、次の境界がどこに立っているかが今の版で見えます。

切り分け(うまくいかないとき)

  • Jupyter のノートブックでは落ちない:ノートブックのカーネル(ipykernel)は event loop を回し続けているので、get_event_loop() は実行中の loop を返します。代わりに asyncio.run() のほうが拒否されます(RuntimeError: asyncio.run() cannot be called from a running event loop。この拒否は素の Python でも同じで、3.13・3.14 とも実測しました)。セルでは await を直接書けます。ターミナルの IPython は事情が違い、コードのまとまりごとに loop を開始・停止するので、同期のコードから呼んだときに実行中の loop があるとは限りません(当プロジェクトでは実測していません)。
  • 落ちているのがライブラリの中:トレースバックで get_event_loop() を呼んでいるフレームが、async 関数の中かどうかを見てください。async の外(import 時やモジュール初期化時)に呼んでいるライブラリは、そのライブラリ側の更新が要ります。 呼び出し前に asyncio.set_event_loop(asyncio.new_event_loop()) を置けば RuntimeError は消えますが、その loop は誰も回していません。ライブラリがそこにタスクを積む種類のもの(再接続・ハートビート・フラッシュ)なら、積まれた仕事は実行されないままになります——例外は出ません。その loop を run_until_complete() で自分で回すか、ライブラリの呼び出しを asyncio.run() の中へ移してください。
  • Thread-1 (worker) のようにスレッド名が MainThread 以外:これは 3.14 の変更とは別件で、3.11 でも同じエラーになります。そのスレッドで loop を使うなら、スレッド内で asyncio.run() を呼ぶか、asyncio.new_event_loop() + set_event_loop() をスレッド内で実行してください。
  • pytest で落ちる:非同期テストを回すプラグイン(pytest-asyncio など)が loop を用意します。プラグイン側が 3.14 に追随した版になっているかを確認してください。テストコードが直接 get_event_loop() を呼んでいるなら、そこが対象です。
  • 3.13 で予兆を見つけたいpython -W always::DeprecationWarning -m pytest で抑制を外します。ただし上の節のとおり、1 回の実行で見えるのは最初の 1 箇所だけです(最初の呼び出しが loop を設定してしまうため)。直して再実行を繰り返してください。
  • 3.14 の他の変更も先に知っておきたい:同じ 3.14 で、multiprocessing の既定 start methodsqlite3 の名前つき placeholder も、3.13 まで通っていた形が落ちるようになりました。

検証環境

  • python@sha256:b877e50b...python:3.14-slim / Python 3.14.6・Linux)、ネットワーク不要
  • 再現:asyncio.get_event_loop() を実行中の loop の外で呼ぶと RuntimeError: There is no current event loop in thread 'MainThread' で終了コード 1
  • 修正:入口を asyncio.run(main()) にし、loop は asyncio.get_running_loop() で取ると終了コード 0

再現から修正までは errfix の検証ハーネスが機械的に確認しています。本文の版ごとの表(3.11 は無言で作る/3.12・3.13 は DeprecationWarning つきで作る/3.14 は RuntimeError)、非メインスレッドでは 3.11 から同じエラーだったこと、実行中の loop の中では 3.14 でも get_event_loop() が通ること、new_event_loop() + set_event_loop() + run_until_complete() が 3.14 でも動くこと、get_event_loop_policy() が 3.14 で「3.16 で削除」と警告すること、3.13 では -W なしでも __main__ からの呼び出しなら警告が出て、パッケージ側のモジュールからの呼び出しでは出ないこと同じスレッドの 2 回目の get_event_loop() は警告を出さないことは、いずれも公式 python イメージの 3.11・3.12・3.13・3.14 で実測しています。ターミナルの IPython と Jupyter のカーネルの挙動は実測していません。

検証(machine-verified)

この修正は python@sha256:b877e50bd90de10af8d82c57a022fc2e0dc731c5320d762a27986facfc3355c1 のバージョン固定コンテナ内で再現し、修正後に There is no current event loop in thread 'MainThread' のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs python/asyncio-get-event-loop-no-current
● reproduce RuntimeError: There is no current event loop in thread 'MainThread' present ✓
● apply fix exit 0
● re-run RuntimeError: There is no current event loop in thread 'MainThread' gone ✓
PASS verified · python@sha256:b877e50bd90de10af8d82c57a022fc2e0dc731c5320d762a27986facfc3355c1 · signature gone

確認したのは上のイメージの中だけです。別の環境で直らなかった、記述が違う、という場合は 報告してください(対象と検証イメージは件名・本文に入ります)。