💡 asyncio.gather() runs coroutines concurrently on a single thread by interleaving them on the event loop. When one coroutine awaits (I/O, sleep), the event loop runs another. Unlike threads, there's no GIL issue and no data races — only one coroutine runs at a time, but they overlap on I/O waits.
=== Sequential (await one at a time) ===
Hello from Alice!
Hello from Bob!
Hello from Carol!
Sequential: 0.091s
=== Concurrent (asyncio.gather) ===
Hello from Bob!
Hello from Alice!
Hello from Carol!
Concurrent: 0.041s
Results: ['Hello from Alice!', 'Hello from Bob!', 'Hello from Carol!']
docker run --rm zchencow/innozverse-python:latest python3 -c "
import asyncio
async def slow_operation(name: str, seconds: float) -> str:
print(f' [{name}] starting ({seconds}s)')
await asyncio.sleep(seconds)
print(f' [{name}] done')
return f'{name}: complete'
async def main():
# Tasks — schedule coroutines without awaiting immediately
task1 = asyncio.create_task(slow_operation('Download', 0.05))
task2 = asyncio.create_task(slow_operation('Process', 0.03))
task3 = asyncio.create_task(slow_operation('Upload', 0.04))
# Can do other work while tasks run
print('Tasks scheduled, doing other work...')
await asyncio.sleep(0.01)
print('Still running...')
# Wait for all
results = await asyncio.gather(task1, task2, task3)
print('All done:', results)
# Timeout
print()
print('=== Timeout ===')
try:
result = await asyncio.wait_for(
slow_operation('Slow', 1.0),
timeout=0.05
)
except asyncio.TimeoutError:
print(' Operation timed out!')
# Cancellation
print()
print('=== Cancellation ===')
async def cancellable():
try:
print(' Cancellable: started')
await asyncio.sleep(1.0)
print(' Cancellable: done (should not print)')
except asyncio.CancelledError:
print(' Cancellable: cancelled gracefully')
raise # re-raise is important
task = asyncio.create_task(cancellable())
await asyncio.sleep(0.02)
task.cancel()
try:
await task
except asyncio.CancelledError:
print(' Task was cancelled')
# gather with return_exceptions
print()
print('=== gather with errors ===')
async def may_fail(n: int):
await asyncio.sleep(0.01)
if n == 2: raise ValueError(f'Task {n} failed')
return f'task-{n} ok'
results = await asyncio.gather(
may_fail(1), may_fail(2), may_fail(3),
return_exceptions=True
)
for r in results:
if isinstance(r, Exception):
print(f' Error: {r}')
else:
print(f' OK: {r}')
asyncio.run(main())
"
Tasks scheduled, doing other work...
Still running...
[Process] done
[Upload] done
[Download] done
All done: ['Download: complete', 'Process: complete', 'Upload: complete']
=== Timeout ===
[Slow] starting (1.0s)
Operation timed out!
=== Cancellation ===
Cancellable: started
Cancellable: cancelled gracefully
Task was cancelled
=== gather with errors ===
OK: task-1 ok
Error: Task 2 failed
OK: task-3 ok