Python の asyncio で「RuntimeError: Event loop is closed」が解消できない
`RuntimeError: Event loop is closed` は asyncio のイベントループを閉じた後にコルーチンやコールバックが実行されようとした際に発生します。
`asyncio.run()` に任せ、Windows では `WindowsSelectorEventLoopPolicy` に切り替えるのが定番の対処です。
#asyncio#event-loop#runtimeerror#aiohttp
公開:
要約
RuntimeError: Event loop is closed は asyncio のイベントループを閉じた後にコルーチンやコールバックが走ろうとした際の例外です。
多くは Windows + aiohttp の組み合わせ、もしくは loop.close() を自前で呼んだケースで再現します。asyncio.run() に任せ、Windows では WindowsSelectorEventLoopPolicy に切り替えるのが定番の対処です。
よくある原因
- Windows + Python 3.8 以降のデフォルト ProactorEventLoop と aiohttp の SSL クローズ処理の組み合わせ
asyncio.run()の後にさらにloop.close()を呼んでいる- プログラム終了直前に SSL transport などのファイナライザがクローズ済みループへ await する
- 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() で入れ子を許可します。