Python:手把手教你 async 與 await

在 Python 的世界裡,效能一直是大家討論的熱點。當你遇到需要處理大量網路請求、讀寫大檔案或與資料庫頻繁互動時,傳統的「同步」執行方式往往會讓程式卡在那邊等,效率低得驚人。

這時候,asyncawait 就派上用場了!

什麼是異步編程?

想像一下,你正在準備晚餐。blog index image

  • 同步 (Synchronous):你先把水煮開,盯著鍋子看,等水開了才去切菜。切完菜,你才去熱鍋炒菜。每件事都必須等上一件做完才能開始。
  • 異步 (Asynchronous):你先把水放上去煮,趁水還沒開的時候,你跑去切菜。切菜的過程中,如果水開了(發出嗶嗶聲),你就轉身去處理水。你不需要在那邊乾等水開。

在程式中,當我們發送一個網路請求(煮水),CPU 其實是在「等」網路回傳(水開)。異步編程就是讓 CPU 在等待的期間可以先去處理別任務(切菜),從而大大提昇效率。

核心關鍵字:async 與 await

在 Python 3.5 之後,官方引入了這兩個語法糖:

1. async def:定義協程 (Coroutine)

平常我們用 def 定義函數,但如果要定義一個異步函數,我們要在前面加上 async

1
2
3
4
5
6
7
import asyncio

async def say_hello():
print("Hello...")
# 這裡會模擬一個耗時操作
await asyncio.sleep(1)
print("...World!")

呼叫一個 async 函數時,它不會立即執行裡面的代碼,而是會回傳一個「協程物件」。

2. await:等待結果

當你執行到一個需要等待的操作(例如 asyncio.sleep 或網路請求)時,使用 await。這會告訴 Python:「我現在要在這裡等一下,你可以先去忙別的事,等這件事處理好了再回來找我。」

注意: await 只能在 async 定義的函數內部使用。

實際範例:比比看誰快

假設我們要執行三個耗時 1 秒的任務。

同步版本 (慢)

1
2
3
4
5
6
7
8
9
10
11
12
13
import time

def task(n):
print(f"任務 {n} 開始")
time.sleep(1) # 阻塞式等待
print(f"任務 {n} 完成")

start = time.time()
task(1)
task(2)
task(3)
print(f"總耗時: {time.time() - start:.2f} 秒")
# 輸出:總耗時: 3.00 秒

異步版本 (快)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import asyncio
import time

async def async_task(n):
print(f"任務 {n} 開始")
await asyncio.sleep(1) # 非阻塞式等待
print(f"任務 {n} 完成")

async def main():
# 同時啟動多個任務
await asyncio.gather(
async_task(1),
async_task(2),
async_task(3)
)

start = time.time()
asyncio.run(main())
print(f"總耗時: {time.time() - start:.2f} 秒")
# 輸出:總耗時: 1.01 秒

可以看到,異步版本幾乎只花了 1 秒就完成了所有任務,因為它在等待第一個任務時,就已經開始處理第二個和第三個了。

常見的陷阱:為什麼我的程式還是很慢?

很多人剛開始用 asyncio 時會遇到一個問題:為什麼加上了 async 還是沒變快?

最常見的原因是在 async 函數中使用了「同步」的阻塞代碼。

1
2
3
4
5
6
7
import asyncio
import time

async def broken_task():
# 錯誤示範!
time.sleep(1) # 這會把整個 Event Loop 鎖住,別人在這段時間也動彈不得
await asyncio.sleep(1)

如果你在異步環境中用了 time.sleep()requests.get() 或任何不支援異步的資料庫驅動,你的程式依然會像同步執行一樣一個一個排隊。

結語

asyncawait 是 Python 處理高併發 (High Concurrency) 的利器。雖然它的學習曲線稍微陡峭一點,但一旦掌握了「不要阻塞 Event Loop」這個核心觀念,你就能寫出極其高效的 Python 程式。

下次如果你要爬取上百個網頁,或開發一個高效的後端 API,別忘了試試看 asyncio 喔!

也許你也會想看看