Lập trình bất đồng bộ với asyncio: async và await

Gọi 50 API, mỗi lần chờ 1 giây — chạy tuần tự mất 50 giây, nhưng thực ra CPU chỉ ngồi chờ. asyncio cho phép chờ đồng thời, rút xuống còn hơn 1 giây.

Hàm bất đồng bộ đầu tiên

import asyncio

async def chao(ten, giay):
    await asyncio.sleep(giay)     # nhường quyền trong lúc chờ
    return f"Xin chào {ten}"

async def main():
    ket_qua = await chao("An", 1)
    print(ket_qua)

asyncio.run(main())

async def tạo một coroutine. Gọi nó không chạy ngay — phải await hoặc đưa vào vòng lặp sự kiện.

Điểm mấu chốt: gather

async def main():
    ket_qua = await asyncio.gather(
        chao("An", 1),
        chao("Bình", 1),
        chao("Cường", 1),
    )
    print(ket_qua)   # mất ~1 giây, không phải 3

asyncio.run(main())

Gọi nhiều API thật với httpx

import asyncio, httpx

async def lay(client, url):
    r = await client.get(url, timeout=10)
    return url, r.status_code

async def main(urls):
    async with httpx.AsyncClient() as client:
        cong_viec = [lay(client, u) for u in urls]
        for url, ma in await asyncio.gather(*cong_viec):
            print(ma, url)

asyncio.run(main(["https://example.com", "https://python.org"]))

Giới hạn số việc chạy song song

sem = asyncio.Semaphore(10)     # tối đa 10 request cùng lúc

async def lay_co_gioi_han(client, url):
    async with sem:
        return await lay(client, url)

Không giới hạn, bạn dễ bị máy chủ đích chặn hoặc làm nghẽn mạng.

Sai lầm thường gặp

  • Dùng time.sleep() trong hàm async — nó chặn cả vòng lặp; phải dùng asyncio.sleep().
  • Dùng requests thay vì httpx/aiohttp — thư viện đồng bộ sẽ chặn toàn bộ.
  • Quên await — coroutine không chạy và Python cảnh báo “never awaited”.

Khi nào asyncio KHÔNG giúp

asyncio chỉ nhanh với tác vụ chờ I/O: mạng, đọc ghi file, truy vấn database. Với tính toán nặng CPU (xử lý ảnh, mã hóa), nó không giúp gì — lúc đó bạn cần multiprocessing.

Lên đầu trang