Skip to main content

How to Resolve Python RuntimeError: "This event loop is already running" in Asyncio

The RuntimeError: This event loop is already running is a common hurdle for developers using Python's asyncio library. It signals an attempt to start or run an event loop when one is already active in the current thread. This often arises in environments like web servers, GUI applications, or Jupyter notebooks where an event loop might already be managed externally, or when incorrectly nesting asyncio calls.

This guide explains the root cause and provides clear, practical solutions.

Understanding the Error: Nested Event Loops

Python's asyncio typically manages a single event loop per thread. Functions like asyncio.run() and loop.run_until_complete() are designed to start and manage the execution of this loop. The error occurs when you attempt to call one of these loop-starting functions from code that is itself already being executed by an active asyncio event loop.

# Error Example
import asyncio

async def my_coroutine():
print("Running my_coroutine")
await asyncio.sleep(0.1) # Simulate some async work

async def main():
print("Inside main - loop is running")
# INCORRECT: Trying to start a new loop or run synchronously
# within an already running loop via asyncio.run()
try:
asyncio.run(my_coroutine()) # Problem: This tries to START a new loop
except RuntimeError as e:
print(f"Caught error: {e}")

# Also INCORRECT if loop is already running from the initial asyncio.run(main())
# try:
# loop = asyncio.get_running_loop()
# loop.run_until_complete(my_coroutine()) # Problem: Blocking call on running loop
# except RuntimeError as e:
# print(f"Caught error: {e}")


# This initial call starts the event loop for main()
print("Starting outer asyncio.run")
asyncio.run(main())
print("Finished outer asyncio.run")

Example Output:

Starting outer asyncio.run
Inside main - loop is running
Caught error: This event loop is already running
Finished outer asyncio.run
  • The top-level asyncio.run(main()) starts the event loop to execute main().
  • Inside main(), calling asyncio.run(my_coroutine()) or loop.run_until_complete(...) is problematic because the loop initiated by the outer asyncio.run() is already running. You can not nest calls that try to start or blockingly run the loop from within code managed by that same loop.

The Core Solution: Use await Inside Async Functions

The fundamental way to execute another coroutine from within an async function is to use the await keyword. This tells the event loop to pause the current coroutine and run the awaited coroutine until it completes, without trying to start a new loop.

# Correct Solution using await
import asyncio

async def my_coroutine():
print("Running my_coroutine")
await asyncio.sleep(0.1)
print("Finished my_coroutine")

async def main():
print("Inside main")
# CORRECT: Use await to run the coroutine within the current loop
await my_coroutine()
print("Back in main after await")
await my_coroutine() # Can await multiple times
print("Finished second await in main")

print("Starting asyncio.run")
asyncio.run(main())
print("Finished asyncio.run")

Example Output:

Starting asyncio.run
Inside main
Running my_coroutine
Finished my_coroutine
Back in main after await
Running my_coroutine
Finished my_coroutine
Finished second await in main
Finished asyncio.run
  • When main() awaits my_coroutine(), the event loop switches context to run my_coroutine() and then returns control back to main() once my_coroutine() finishes. This all happens within the single event loop started by the top-level asyncio.run(main()).

Workaround: Using nest_asyncio (for Specific Environments)

In certain environments (like Jupyter Notebooks, Spyder, or some GUI frameworks) where an event loop is already running implicitly in the background, you might still encounter the error even without explicit nesting in your own code. In these cases, the nest_asyncio library can be a useful workaround.

  1. Install nest_asyncio:

    pip install nest-asyncio
    # or using pip3
    pip3 install nest-asyncio
  2. Apply the patch: Add nest_asyncio.apply() at the beginning of your script or notebook cell.

    # Example using nest_asyncio (often needed in Jupyter)
    import asyncio
    import nest_asyncio

    nest_asyncio.apply() # Apply the patch

    async def my_coroutine():
    print("Running my_coroutine (nested allowed)")
    await asyncio.sleep(0.1)

    async def main():
    print("Inside main (nesting allowed)")
    # Now, even though not ideal style, asyncio.run inside works
    asyncio.run(my_coroutine())
    print("Finished nested run")

    # This outer run might itself be running within an existing loop (e.g., Jupyter)
    print("Starting potentially nested asyncio.run")
    asyncio.run(main())
    print("Finished potentially nested asyncio.run")

    Example Output:

    Starting potentially nested asyncio.run
    Inside main (nesting allowed)
    Running my_coroutine (nested allowed)
    Finished nested run
    Finished potentially nested asyncio.run
note

nest_asyncio modifies the standard asyncio behavior. While effective, prefer the await pattern (Solution 2) in standard Python scripts for cleaner design. Use nest_asyncio primarily when integrating with environments that already manage an event loop.

Manual Loop Management (Advanced/Specific Cases)

While asyncio.run() is the recommended high-level entry point, you might need manual loop management in complex applications or when integrating asyncio with other frameworks.

# Example with Manual Loop Management
import asyncio
import sys

async def my_coroutine():
print("Running my_coroutine")
await asyncio.sleep(0.1)

async def main():
print("Inside main")
await my_coroutine()
print("Coroutine awaited in main")

# --- Manual Loop Setup ---
try:
# Try to get the currently running loop (if any)
loop = asyncio.get_running_loop()
print("Using existing event loop")
except RuntimeError:
# No loop running, create a new one
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
print("Created new event loop")
# --- End Manual Loop Setup ---

try:
print("Starting loop.run_until_complete")
loop.run_until_complete(main())
print("Finished loop.run_until_complete")
finally:
# --- Manual Loop Cleanup (Conditional) ---
# Only close loops you explicitly created and manage
# If loop was obtained via get_running_loop(), closing it might break
# the external application managing it.
# if not loop.is_running() and loop is not asyncio.get_event_loop_policy().get_event_loop():
# loop.close()
# print("Closed manually managed event loop")
pass # Simplified cleanup for this example
  • This shows explicitly getting or creating a loop.
  • loop.run_until_complete() is used instead of asyncio.run().
  • Closing the loop (loop.close()) requires careful consideration of who "owns" the loop. If you created it with new_event_loop(), you should generally close it. If you got it via get_running_loop(), closing it might interfere with the framework that started it.

Creating Tasks Within the Loop

For concurrency, use asyncio.create_task within your async function when managing the loop manually:

# Example with Tasks and Manual Loop
import asyncio
import sys

# --- Get/Create Loop (same as above) ---
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# ---

async def my_coroutine(index):
print(f'ABC {index}')
await asyncio.sleep(0.1)
print(f'XYZ {index}')

async def main():
tasks = []
for index in range(3):
task = loop.create_task(my_coroutine(index)) # Use loop.create_task
# Or asyncio.create_task(my_coroutine(index)) which uses the current loop
tasks.append(task)

await asyncio.gather(*tasks)
print("All tasks finished")

try:
loop.run_until_complete(main())
finally:
# loop.close() # Optional cleanup
pass
  • Tasks are created using loop.create_task() (or asyncio.create_task()) and awaited using asyncio.gather() or asyncio.wait().

Conclusion

The RuntimeError: This event loop is already running typically stems from incorrectly nesting calls that start or manage the asyncio event loop.

  • The primary solution is to use await to run coroutines from within other async functions, ensuring execution happens within the single, active event loop.
  • The nest_asyncio library provides a workaround for environments with pre-existing loops, while manual loop management offers fine-grained control for advanced scenarios.

Understanding these patterns is key to writing correct and efficient asynchronous Python code.