ข้ามไปยังเนื้อหา

asyncio พื้นฐาน

asyncio ทำงานบน event loop: ตัว scheduler ที่เก็บ queue ของ coroutines และ callbacks, รันทีละตัว และสลับระหว่างกันเมื่อตัวใดหยุดที่ expression await

ไม่มีการสร้าง OS threads ใดๆ concurrency เป็นแบบ cooperative — coroutine ต้องยกการควบคุมโดยสมัครใจด้วย await เพื่อให้ coroutines อื่นทำงานได้

import asyncio
async def say(message: str, delay: float) -> None:
await asyncio.sleep(delay)
print(message)
async def main() -> None:
await say("Hello", 0.1)
await say("World", 0.1)
asyncio.run(main())
# Hello
# World

ในตัวอย่างแบบ sequential นี้ say("World", …) ยังไม่เริ่มจนกว่า say("Hello", …) จะเสร็จสิ้น

ฟังก์ชันที่กำหนดด้วย async def คือ coroutine function การเรียกฟังก์ชันนี้ไม่ได้รันเนื้อหา — แต่คืน coroutine object เนื้อหาจะรันเฉพาะเมื่อ coroutine ถูก await หรือถูก schedule บน event loop เท่านั้น

import asyncio
async def greet(name: str) -> str:
return f"Hello, {name}!"
# การเรียก greet() คืน coroutine object — ยังไม่มีอะไรรัน
coro = greet("Python")
print(type(coro)) # <class 'coroutine'>
# Await มันเพื่อรันเนื้อหาจริง
result = asyncio.run(greet("Python"))
print(result) # Hello, Python!

keyword await ใช้ได้เฉพาะภายในฟังก์ชัน async def เท่านั้น await หยุด coroutine ปัจจุบันและยกการควบคุมกลับไปยัง event loop event loop รัน coroutines อื่นที่พร้อมแล้วกลับมาที่ coroutine นี้เมื่อ awaited object เสร็จสิ้น

import asyncio
async def fetch_data(label: str) -> str:
print(f" {label}: starting fetch")
await asyncio.sleep(0.05) # หยุดที่นี่
print(f" {label}: fetch done")
return f"data-{label}"
async def main() -> None:
result = await fetch_data("A")
print(result)
asyncio.run(main())
# A: starting fetch
# A: fetch done
# data-A

asyncio.run(coro) สร้าง event loop ใหม่, รัน coroutine จนเสร็จสมบูรณ์, ปิด loop และคืนค่าผลลัพธ์ เป็น entry point มาตรฐานสำหรับโปรแกรม async

import asyncio
async def compute(x: int) -> int:
await asyncio.sleep(0) # yield ให้ event loop ครั้งหนึ่ง
return x ** 2
result = asyncio.run(compute(7))
print(result) # 49

เรียก asyncio.run ครั้งเดียวต่อโปรแกรม ที่ระดับบนสุด อย่า nest การเรียก asyncio.run — การเรียกภายในจะ raise RuntimeError: This event loop is already running

asyncio.gather(*coroutines) กำหนด schedule coroutines ทั้งหมดพร้อมกันและรอทั้งหมด ผลลัพธ์คืนในลำดับเดียวกับ input coroutines

import asyncio
async def step(label: str, delay: float) -> str:
await asyncio.sleep(delay)
return f"{label} done"
async def main() -> None:
results = await asyncio.gather(
step("A", 0.3),
step("B", 0.1),
step("C", 0.2),
)
print(results)
# ['A done', 'B done', 'C done'] — ลำดับ input เสมอ
# เวลารวม ≈ 0.3 วินาที ไม่ใช่ 0.6 วินาที
asyncio.run(main())

ผลลัพธ์อยู่ใน ลำดับ input เสมอ ไม่ว่าจะเสร็จในลำดับใด ซึ่งเหมือนกับ Promise.all ของ JavaScript

โดยค่าเริ่มต้น หาก coroutine ใด raise, gather จะส่งต่อ exception แรกและยกเลิกที่เหลือ ส่ง return_exceptions=True เพื่อรวบรวม exceptions เป็น values แทน (เหมือน Promise.allSettled)

import asyncio
async def risky(n: int) -> int:
if n == 2:
raise ValueError(f"n={n} is bad")
return n * 10
async def main() -> None:
results = await asyncio.gather(
risky(1),
risky(2),
risky(3),
return_exceptions=True,
)
for r in results:
if isinstance(r, Exception):
print(f"Error: {r}")
else:
print(f"OK: {r}")
asyncio.run(main())
# OK: 10
# Error: n=2 is bad
# OK: 30

asyncio.TaskGroup (3.11+) เป็น structured-concurrency ทางเลือกแทน gather หาก task ใด raise, group จะยกเลิก tasks ที่เหลือทั้งหมดโดยอัตโนมัติ

import asyncio
async def work(label: str, delay: float) -> str:
await asyncio.sleep(delay)
return f"{label} complete"
async def main() -> None:
async with asyncio.TaskGroup() as tg:
task_a = tg.create_task(work("A", 0.2))
task_b = tg.create_task(work("B", 0.1))
# ทั้งสอง task เสร็จสิ้นที่นี่ exceptions ถูก re-raise อัตโนมัติ
print(task_a.result())
print(task_b.result())
asyncio.run(main())
การเรียกฟังก์ชัน `async def` คืนอะไรก่อนที่คุณจะ `await`?
entry point มาตรฐานสำหรับรัน top-level async coroutine คืออะไร?
`asyncio.gather(a(), b(), c())` รับประกันอะไรเกี่ยวกับลำดับผลลัพธ์?
argument ใดใน `asyncio.gather` ทำให้ `gather` คืน exceptions เป็น values แทนการส่งต่อ?