Keywords: Python asynchronous programming | asyncio warnings | coroutine not awaited | aiohttp | performance optimization
Abstract: This article provides an in-depth analysis of the common 'coroutine was never awaited' warning in Python asyncio programming. By comparing synchronous and asynchronous execution mechanisms, it explains the core principles of coroutine object creation and invocation. The article offers complete error resolution strategies, including proper usage of async/await syntax, the asyncio.run() function, and best practices with aiohttp asynchronous HTTP client, demonstrating the full optimization process from blocking to non-blocking asynchronous requests through practical code examples.
Problem Background and Error Phenomenon
When learning Python asynchronous programming, developers frequently encounter the RuntimeWarning: coroutine was never awaited warning error. This error typically occurs when an asynchronous function is defined but not properly awaited for execution. From the provided example code, we can see the developer attempted to convert traditional synchronous HTTP request code into an asynchronous version for performance improvement, but encountered this warning when executing the asynchronous function.
Synchronous vs Asynchronous Execution Mechanism Comparison
To understand this error, it's essential to first clarify the fundamental differences between synchronous and asynchronous functions. Synchronous functions defined with def execute immediately when called and return results. Asynchronous functions defined with async def do not execute immediately when called but return a coroutine object instead.
# Synchronous function example
def synchronous_function():
return "Execute immediately and return result"
# Asynchronous function example
async def asynchronous_function():
return "Return coroutine object, requires awaiting for execution"
print(synchronous_function()) # Output: Execute immediately and return result
print(asynchronous_function()) # Output: <coroutine object asynchronous_function at 0x...>
When an asynchronous function is called but not awaited, the Python interpreter detects this unexecuted coroutine object and issues the coroutine was never awaited warning, alerting developers that this coroutine might have been accidentally overlooked.
In-depth Analysis of Error Causes
In the original problem code, two key issues caused the warning to appear:
Issue One: Asynchronous Function Not Properly Awaited
The function faire_toutes_les_requetes_sans_bloquer() was defined as asynchronous but called directly in the main program without using await or running through an event loop:
# Incorrect usage
faire_toutes_les_requetes_sans_bloquer() # Returns coroutine object, generates warning
# Correct usage
await faire_toutes_les_requetes_sans_bloquer() # Await within async function
# Or
asyncio.run(faire_toutes_les_requetes_sans_bloquer()) # Run as entry point
Issue Two: Confusion Between Synchronous and Asynchronous Context Managers
aiohttp.ClientSession() is an asynchronous context manager that must be used with async with rather than ordinary with statements:
# Incorrect usage
with aiohttp.ClientSession() as session: # Will raise TypeError
# ...
# Correct usage
async with aiohttp.ClientSession() as session:
# ...
Complete Solution
Based on the above analysis, here is the complete fix:
import asyncio
import aiohttp
import datetime
async def requete_sans_bloquer(num, session):
print(f'Get {num}')
async with session.get("https://httpbin.org/uuid") as response:
# Fix parenthesis position - await JSON parsing completion before accessing key
uid = (await response.json())['uuid']
print(f"Res {num}: {uid}")
async def faire_toutes_les_requetes_sans_bloquer():
async with aiohttp.ClientSession() as session:
# Create list of coroutines for all requests
futures = [requete_sans_bloquer(x, session) for x in range(10)]
# Use await to wait for all coroutines to complete
await asyncio.gather(*futures)
print("Fin de la boucle !")
# Main program entry
print("Non bloquant : ")
start = datetime.datetime.now()
# Use asyncio.run() to run top-level coroutine
asyncio.run(faire_toutes_les_requetes_sans_bloquer())
# Use total_seconds() for more precise timing (including microseconds)
exec_time = (datetime.datetime.now() - start).total_seconds()
print(f"Pour faire 10 requêtes, ça prend {exec_time:.3f}s\n")
Difference Between asyncio.gather and asyncio.wait
In asynchronous programming, both asyncio.gather() and asyncio.wait() are important functions for concurrently executing multiple coroutines, but they differ in exception handling and result return:
import asyncio
async def task_with_exception():
raise ValueError("Test exception")
async def normal_task():
return "Normal completion"
# Using asyncio.gather - returns on first exception
async def example_gather():
try:
results = await asyncio.gather(normal_task(), task_with_exception())
except ValueError as e:
print(f"Caught exception: {e}")
# Using asyncio.wait - can control waiting strategy
async def example_wait():
task1 = asyncio.create_task(normal_task())
task2 = asyncio.create_task(task_with_exception())
done, pending = await asyncio.wait([task1, task2], return_when=asyncio.ALL_COMPLETED)
for task in done:
if task.exception():
print(f"Task exception: {task.exception()}")
else:
print(f"Task result: {task.result()}")
Performance Optimization Verification
By comparing the synchronous version with the fixed asynchronous version, significant performance improvements are evident:
- Synchronous Version: Using
requestslibrary, 10 requests executed sequentially, total time approximately 4-5 seconds - Asynchronous Version: Using
aiohttplibrary, 10 requests executed concurrently, total time approximately 0.3-0.5 seconds
This performance improvement is particularly noticeable in I/O-intensive tasks because asynchronous programming allows execution of other tasks while waiting for network responses, rather than blocking the entire program.
Best Practice Recommendations
Based on practical development experience, here are recommended best practices for asynchronous programming:
- Clear Function Responsibilities: Separate event loop management from business logic, use
asyncio.run()at the top level - Proper Context Manager Usage: Asynchronous resources must be managed with
async with - Exception Handling: Use
asyncio.gather()withreturn_exceptions=Trueparameter or explicitly check task exceptions - Resource Cleanup: Ensure all asynchronous resources are properly closed when coroutines end
- Performance Monitoring: Use precise timing methods like
total_seconds()to evaluate optimization effects
By following these practices, common asynchronous programming pitfalls can be avoided, enabling the construction of efficient and reliable asynchronous applications.