2025, Oct 17 11:16
Как запускать asyncio-код в Jupyter Notebook без ошибок
Разбираем ошибку RuntimeError при asyncio.run в Jupyter Notebook: почему конфликтует событийный цикл и как правильно запускать корутины с top-level await.
Запуск asyncio‑кода внутри Jupyter Notebook часто сбивает с толку. Обычный для скриптов на Python приём — вызов asyncio.run() — в ноутбуках приводит к RuntimeError. Причина проста, и решение тоже — важно лишь корректно работать с ноутбуком, чтобы не конфликтовать с его событийным циклом.
Как воспроизвести проблему
Этот фрагмент работает в отдельном .py‑скрипте, но вызывает ошибку в ячейке ноутбука:
import asyncio
async def do_job():
    await asyncio.sleep(1)
    return "Done"
asyncio.run(do_job())
В среде Jupyter это обычно завершается так:
RuntimeError: Event loop is closed
Что происходит на самом деле
Под капотом Jupyter уже запущен событийный цикл. asyncio.run() пытается создать и полностью управлять новым циклом с нуля. Когда цикл уже активен, запуск ещё одного не допускается, и этот конфликт и приводит к увиденной ошибке. В обычном .py‑скрипте никакого цикла нет, пока вы его не запустите, поэтому asyncio.run() без помех создаёт его и затем корректно завершает.
Как корректно запускать асинхронный код в Jupyter
Не запускайте новый цикл. Просто ожидайте корутину напрямую в ячейке. Jupyter поддерживает top-level await, поэтому можно вызывать асинхронные функции без обёртки в asyncio.run().
import asyncio
async def do_job():
    await asyncio.sleep(1)
    return "Done"
await do_job()
Так вы используете уже существующий цикл ноутбука и полностью избегаете конфликта.
Почему это важно
Поведение асинхронного кода зависит от среды выполнения. В .py‑скрипте вызов asyncio.run() уместен, потому что активного цикла ещё нет, пока вы его не создадите. В Jupyter Notebook цикл уже работает, и попытка запустить ещё один через asyncio.run() завершится неудачей. Понимание, где вы находитесь, избавляет от запутанных ошибок и помогает писать асинхронный код, который выполняется предсказуемо.
Вывод
В ноутбуке используйте top-level await и вызывайте корутину напрямую. В обычном скрипте управляйте циклом через asyncio.run(). Этого простого разграничения достаточно, чтобы асинхронные сценарии стабильно работали в обеих средах.
Статья основана на вопросе на StackOverflow от Salika Ansari и ответе от Yokozuna.