できない.dev

Python の asyncio で「RuntimeError: Event loop is closed」が解消できない

RuntimeError: Event loop is closed は asyncio のイベントループを閉じた後にコルーチンやコールバックが実行されようとした際に発生します。asyncio.run() に任せ、Windows では WindowsSelectorEventLoopPolicy に切り替えるのが定番の対処です。

公開: 更新:

要約

RuntimeError: Event loop is closed は asyncio のイベントループを閉じた後にコルーチンやコールバックが走ろうとした際の例外です。
多くは Windows + aiohttp の組み合わせ、もしくは loop.close() を自前で呼んだケースで再現します。asyncio.run() に任せ、Windows では WindowsSelectorEventLoopPolicy に切り替えるのが定番の対処です。

よくある原因

  1. Windows + Python 3.8 以降のデフォルト ProactorEventLoop と aiohttp の SSL クローズ処理の組み合わせ
  2. asyncio.run() の後にさらに loop.close() を呼んでいる
  3. プログラム終了直前に SSL transport などのファイナライザがクローズ済みループへ await する
  4. Jupyter Notebook 内で asyncio.run() を呼び、既存ループと衝突している

解決策

1. Windows ではセレクタ系ポリシーに切り替える

aiohttp の README にも記載のある既知の回避策です。asyncio.run() を呼ぶ前に一度だけポリシーを設定します(公式ドキュメント(新しいタブで開く))。

import asyncio
import sys
 
if sys.platform == "win32":
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
 
asyncio.run(main())

2. ループを自前で閉じない

loop = asyncio.new_event_loop()
loop.run_until_complete(main())
loop.close()
asyncio.run(other())

上の書き方は asyncio.run が新しいループを作るタイミングで閉じ忘れの transport を踏みます。
代わりに asyncio.run だけに統一します。

asyncio.run(main())
asyncio.run(other())

3. 残タスクを掃除してから終了する

async def main():
    await do_work()
    pending = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
    for t in pending:
        t.cancel()
    await asyncio.gather(*pending, return_exceptions=True)
 
asyncio.run(main())

4. Jupyter / IPython では asyncio.run を使わない

Jupyter のセルでは既にイベントループが回っているため、asyncio.run() は失敗します。
トップレベルで await main() と書くか、どうしても asyncio.run() を使いたければ nest_asyncio.apply() で入れ子を許可します。

実行例

実際に上記のコードを python:3.12 環境で動かすと、閉じたループへの run_until_complete 呼び出しが RuntimeError: Event loop is closed を送出して終了コード 1 で終了し、asyncio.run() に統一することで 2 回目も正常に完了(終了コード 0)することが確認できる。

$ python --version
Python 3.12.13
$ python bad.py
1回目: done
2回目(閉じたループを再利用):
Traceback (most recent call last):
  File "/tmp/tmp.y2ZCN04DZc/bad.py", line 12, in <module>
    print(loop.run_until_complete(work()))
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/asyncio/base_events.py", line 666, in run_until_complete
    self._check_closed()
  File "/usr/local/lib/python3.12/asyncio/base_events.py", line 545, in _check_closed
    raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
sys:1: RuntimeWarning: coroutine 'work' was never awaited
終了コード: 1
$ python good.py
1回目: done
2回目: done
終了コード: 0

— 2026-08-02 時点の出力

この記事は役立ちましたか?