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 が無い状態のメインスレッドで呼ぶと、版ごとにこうなります。
| Python | asyncio.get_event_loop()(loop が無いメインスレッド) |
|---|---|
| 3.11 | loop を作って返す(警告なし) |
| 3.12 | DeprecationWarning: There is no current event loop を出して、loop を作って返す(終了コード 0) |
| 3.13 | 同上(終了コード 0) |
| 3.14 | RuntimeError: 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 method とsqlite3の名前つき 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 のカーネルの挙動は実測していません。