Glossary
Asyncio
Most Python code executes from top to bottom, one step at a time.
That model breaks when your program has to wait on something, like a network call or a file read.
asyncio
is Python’s answer. It uses an event loop to run multiple tasks on a single thread. No threads. No processes. Just tasks that pause and resume with async
and await
.
What Is asyncio
asyncio
is a standard library in Python that lets you write asynchronous code. It helps you manage concurrency without using threads or multiple processes.
It’s designed for I/O-bound operations: reading files, making HTTP requests, or waiting for database results. When one task waits, another starts. All in one thread.
At the core of asyncio
:
async def
await
- The event loop runs all scheduled tasks
- Tasks and Futures let you coordinate execution
You can:
- Use
asyncio.run
()
to launch the main coroutine - Use
asyncio.gather()
- Use
create_task()
to schedule background work - Use
TaskGroup
to manage related tasks with better structure
Event Loop and Coroutines
The event loop is the engine behind all of it. It runs coroutines, switches between tasks, and resumes work once each pause is over.
Define a coroutine:
async def main():
await asyncio.sleep(1)
But it won't run until you execute:
asyncio.run(main()
That starts the event loop and drives everything to completion.
Calling a coroutine function returns a coroutine object. It doesn't execute by default. You must await it or schedule it with create_task()
.
Example: Concurrent Tasks
import asyncio
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
await asyncio.gather(
say_after(1, 'hello'),
say_after(2, 'world')
)
asyncio.run(main())
This prints "hello" after 1 second, then "world" after 2 seconds. Because they run together, total runtime is 2 seconds. If run sequentially, it would take 3.
Coroutine Objects vs Coroutine Functions
When you call a coroutine function, it returns a coroutine object. It does nothing until you await it.
coro = main() # coroutine object
asyncio.run(coro) # executes it
Using create_task()
async def delayed():
await asyncio.sleep(1)
print("Done")
async def main():
task = asyncio.create_task(delayed())
print("Task scheduled")
await task
asyncio.run(main())
create_task()
schedules the coroutine to run soon. The event loop takes care of when.
TaskGroup for Structured Concurrency
TaskGroup
makes it easier to manage multiple tasks. It waits for all of them to complete. If one fails, it cancels the others.
import asyncio
async def job(n, delay):
await asyncio.sleep(delay)
print(f"Job {n} done after {delay}s")
async def main():
async with asyncio.TaskGroup() as tg:
tg.create_task(job(1, 1))
tg.create_task(job(2, 2))
tg.create_task(job(3, 0.5))
print("All jobs completed")
asyncio.run(main())
The async with
block ensures all tasks are done or canceled before it exits.
Timeouts and Cancellation
Tasks can be canceled by calling cancel()
or by using asyncio.timeout()
:
import asyncio
async def cleanup_task():
try:
while True:
print("Working...")
await asyncio.sleep(1)
finally:
print("Cleaning up before exit.")
async def main():
task = asyncio.create_task(cleanup_task())
await asyncio.sleep(3)
task.cancel()
try:
await task
except asyncio.CancelledError:
print("Task was cancelled")
asyncio.run(main())
Use asyncio.timeout()
to limit duration:
async def main():
try:
async with asyncio.timeout(2):
await asyncio.sleep(5)
except TimeoutError:
print("Operation timed out")
Integration with Files, HTTP, and CPU Work
Use async libraries to avoid blocking the event loop.
Async File I/O
import aiofiles
import asyncio
async def write_log(message):
async with aiofiles.open('log.txt', mode='a') as f:
await f.write(message + '\n')
async def main():
await asyncio.gather(
write_log("Started A"),
write_log("Started B")
)
asyncio.run(main())
Async HTTP Requests
import aiohttp
import asyncio
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
return await resp.text()
async def main():
urls = ['https://example.com', 'https://httpbin.org']
pages = await asyncio.gather(*(fetch(url) for url in urls))
for content in pages:
print(len(content))
asyncio.run(main())
CPU-Bound Work
Use run_in_executor()
to handle blocking or CPU-heavy code.
import asyncio
import time
def blocking_task():
time.sleep(2)
return "done"
async def main():
loop = asyncio.get_running_loop()
result = await loop.run_in_executor(None, blocking_task)
print(result)
asyncio.run(main())
For more parallelism, use ProcessPoolExecutor
.
Thread Safety
asyncio
is single-threaded. You don’t need to worry about locks unless you add threads yourself.
If coroutines share state, use asyncio.Lock
:
lock = asyncio.Lock()
async def update():
async with lock:
# modify shared state
...
Or use asyncio.Queue
:
queue = asyncio.Queue()
async def producer():
await queue.put("data")
async def consumer():
item = await queue.get()
print("Received:", item)
I/O vs CPU: Know the Difference
Use asyncio
when waiting. Use processes when computing.
Mixing Async and Blocking Code
Wrap sync functions with run_in_executor()
:
async def run_blocking():
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, blocking_function)
Never run blocking code directly inside an async def
. It will freeze the loop.
FAQ
What is asyncio
in Python?
A library to write concurrent, asynchronous code using async
and await
. It runs in a single thread using an event loop.
Is asyncio
multi-threaded?
No. It uses one thread with cooperative multitasking.
How does it compare to threading or multiprocessing?
asyncio
: single-threaded, best for I/O-bound workthreading
: multiple threads, but harder to managemultiprocessing
: best for CPU-heavy tasks
When should I use asyncio
?
When your code waits on network, files, or database responses. It’s efficient and avoids thread overhead.
What is a coroutine object?
It’s what you get when you call an async def
function. You must await
or schedule it to run.
What’s the difference between await
, gather
, and create_task
?
await
: waits for a coroutine to finishgather
: runs several coroutines and waits for allcreate_task
: schedules a coroutine to run soon
What is a Future?
A placeholder for a result that’s not ready. Managed by asyncio
.
Why use TaskGroup?
To manage a group of tasks with one structure. If one fails, the others are canceled. It simplifies cleanup.
Does asyncio support timeouts and canceling?
Yes. Use task.cancel()
or asyncio.timeout()
and handle with try/finally
.
Can I run blocking code in asyncio?
Yes, but use run_in_executor()
to avoid freezing the loop.
Is asyncio
good for CPU-bound tasks?
No. Use multiprocessing or a thread pool.
What libraries should I use?
aiofiles
for filesaiohttp
for HTTPrun_in_executor()
for sync code
Is asyncio stable?
Yes. It’s widely used in production systems and actively maintained.
What Python version should I use?
Python 3.7 or newer. That gives you [asyncio.run](<http://asyncio.run/>)()
, create_task()
, and TaskGroup
.
Summary
asyncio
is Python’s built-in tool for writing fast, non-blocking programs.
It is not a thread-based system. It runs an event loop, pauses tasks when they wait, and resumes them when ready. One thread. Many tasks. Maximum efficiency for I/O-heavy workloads.
You’ve learned how to use async def
, await
, gather
, and create_task
. You’ve seen TaskGroup
for coordination, and run_in_executor()
for legacy or CPU-bound work. You know when to use locks or queues. You know the right tools for files, HTTP, and long-running functions.
Use asyncio
when your code waits.
Keep blocking code isolated.
Structure your app with clear, predictable task flows.
If your program does a lot of waiting, asyncio
makes it move.
A wide array of use-cases
Discover how we can help your data into your most valuable asset.
We help businesses boost revenue, save time, and make smarter decisions with Data and AI