Trong bài viết này, mình sẽ chia sẻ lại quá trình tìm hiểu của bản thân về cách hoạt động của async/await trong FastAPI, từ những ví dụ cơ bản đến cách mà các tác vụ bất đồng bộ vận hành bên dưới thông qua event loop. Mình tin rằng đây là chủ đề dễ gây nhầm lẫn khi mới làm việc với Python async hoặc FastAPI.
Note:
Bài viết được biên soạn dựa trên quá trình tìm hiểu cá nhân, có sử dụng AI (ChatGPT
) để hỗ trợ giải thích và sắp xếp nội dung cho dễ hiểu hơn.
1. Tình huống ban đầu: một đoạn code bất thường
Giả sử bạn có một API FastAPI như sau:
from fastapi import FastAPI
import time
import asyncio app = FastAPI() @app.get("/bad_async")
async def bad_async(): time.sleep(5) return {"msg": "bad async"} @app.get("/slow")
async def slow_non_blocking(): await asyncio.sleep(5) return {"msg": "done"} @app.get("/quick")
async def quick(): return {"msg": "quick response"}
Nếu bạn gọi API /bad_async
trước, rồi ngay sau đó gọi tiếp /quick
, thì /quick
sẽ bị chặn 5 giây. Nhưng nếu thay vào đó là gọi /slow
rồi gọi /quick
, thì /quick
phản hồi ngay lập tức. Tại sao lại có sự khác biệt như vậy?
📚 Tài liệu tham khảo – FastAPI: Async and Await
2. Event loop là gì?
Giải thích đơn giản
Hãy tưởng tượng event loop giống như một nhân viên trực tổng đài, nhiệm vụ là:
- Lần lượt nhận các yêu cầu đến.
- Giao nhiệm vụ cho các tác vụ có thể xử lý ngay.
- Nếu tác vụ đang chờ đợi một thứ gì đó (I/O), thì nó nhường lại quyền xử lý cho tác vụ khác.
📚 Python Docs – asyncio Event Loop
Minh họa thực tế
await asyncio.sleep(5)
giống như một người nói: “Tôi đang chờ 5 giây, ai khác cần xử lý gì thì vào trước đi”.time.sleep(5)
giống như một người chiếm luôn ghế 5 giây, dù không làm gì cả.
Kết quả là khi event loop gặp await asyncio.sleep(5)
→ nó chuyển sang xử lý tiếp những việc khác (ví dụ /quick
). Nhưng gặp time.sleep(5)
→ bị đứng hình toàn bộ.
3. Vì sao đổi async def
thành def
thì lại không bị block?
Nếu bạn sửa lại đoạn code:
@app.get("/bad_async")
def bad_async(): time.sleep(5) return {"msg": "bad async"}
Thì bất ngờ là /quick
lại phản hồi ngay lập tức sau khi gọi /bad_async
!
Tại sao?
Khi bạn dùng def
, FastAPI hiểu rằng đây là một tác vụ đồng bộ → nó không chạy trong event loop chính, mà sẽ được đưa sang một thread phụ bằng ThreadPoolExecutor
(có sẵn trong FastAPI/Uvicorn).
📚 Python Docs – ThreadPoolExecutor
Điều đó có nghĩa là:
time.sleep(5)
chạy trong thread phụ, không ảnh hưởng đến event loop./quick
vẫn được event loop xử lý bình thường.
4. Khi một async task nhường event loop, nó được xử lý ở đâu?
Nó không được xử lý ở đâu cả trong lúc chờ đợi. Chính là:
Task đó bị tạm dừng, và event loop đi làm việc khác.
Sau khi việc chờ đợi (chẳng hạn await asyncio.sleep(5)
) hoàn thành → event loop đánh thức lại task đó để tiếp tục.
📚 Real Python – Async IO in Python: A Complete Walkthrough
5. Còn aiofiles
và httpx
thì sao?
Cũng tương tự, khi bạn làm:
async with aiofiles.open("data.txt", mode="r") as f: contents = await f.read() response = await httpx.get("https://example.com")
Đây là những tác vụ I/O bất đồng bộ → khi await
, event loop sẽ tạm dừng task hiện tại và đi xử lý tác vụ khác trong khi đợi tác vụ I/O này hoàn tất.
Vậy ai sẽ "theo dõi" để báo lại cho event loop biết khi nào tác vụ đã xong?
Câu trả lời là:
📡 OS Kernel + Epoll/IOCP + lib như uvloop
(hoặc mặc định asyncio
loop).
- Ở mức thấp, Python sử dụng
selectors
(trên Linux làepoll
, trên Windows làIOCP
) để "đăng ký" các socket hoặc file descriptor và chờ sự kiện I/O. - Khi một socket/file đã sẵn sàng để đọc/ghi, hệ điều hành sẽ "báo hiệu" lại cho event loop.
asyncio
hoặcuvloop
sẽ nhận thông báo này, và đưa task đang đợi đó quay lại vào hàng đợi để tiếp tục chạy.
📚 httpx Documentation – Async Support
📚 aiofiles GitHub Repository
📚 uvloop GitHub
6. Tổng kết
Tình huống | Có nên dùng async không? |
Cần tránh gì? |
---|---|---|
Gọi đến API bất đồng bộ (httpx) | ✅ Có | ❌ Không dùng thư viện sync |
Đọc ghi file (aiofiles) | ✅ Có | ❌ Không dùng open/read thường |
Xử lý nặng CPU | ❌ Không | ✅ Nên dùng thread/process pool |
Dùng time.sleep() |
❌ Không | ✅ Thay bằng await asyncio.sleep() |
📚 Tài liệu tham khảo tổng hợp
- https://fastapi.tiangolo.com/async/
- https://docs.python.org/3/library/asyncio.html
- https://realpython.com/async-io-python/
- https://www.python-httpx.org/async/
- https://github.com/Tinche/aiofiles
- https://github.com/MagicStack/uvloop
Hy vọng bài viết này giúp bạn hiểu rõ hơn về async/await và event loop trong Python/FastAPI. Chúc bạn code hiệu quả hơn và tránh được những bug khó chịu do block event loop!