什麼是 asyncio?——Python 的非同步編程核心

更新日期: 2025 年 2 月 13 日

在 Python 開發中,同步(synchronous) 程式設計模式通常會讓程式執行一個任務時,必須等待該任務完成後才能繼續執行其他操作。

這樣的方式對於 I/O 密集型應用(如網路請求、檔案讀寫、資料庫查詢)來說,效能可能會受到影響,因為程式執行時可能會大量時間花在等待結果返回。

為了解決這個問題,Python 提供了 asyncio 這個標準庫,幫助開發者進行 非同步(asynchronous) 程式開發。

使程式能夠同時處理多個 I/O 任務,而不必等待其中一個任務完成後才開始下一個。這對於提升應用效能、減少等待時間有很大幫助。

本文將深入介紹 asyncio 的概念、核心功能、應用場景,並透過實際範例來展示如何在 Python 中實現非同步程式設計。


asyncio 是什麼?

asyncioPython 內建的非同步編程庫,主要用於:

  • 協程(coroutine):允許函式以非同步方式運行,而不會阻塞程式。
  • 事件迴圈(event loop):管理和執行多個非同步任務,確保它們能夠有效率地運行。
  • 異步 I/O(asynchronous I/O):適用於網路請求、資料庫查詢、檔案讀寫等需要等待的操作,讓程式可以在等待時執行其他任務。

與傳統的 多執行緒(threading)多行程(multiprocessing) 不同,asyncio 採用了 單執行緒+事件驅動 的方式。

透過 協程 來達成並發效果,而不需要額外創建多個執行緒或進程,因此記憶體消耗較少,適合高併發應用。

asyncio 運作機制示意圖

以下是 asyncio 的工作流程:

1️⃣ 事件迴圈(Event Loop) 啟動並監聽所有協程
2️⃣ 當一個協程遇到 I/O 操作時(如等待 API 回應),事件迴圈會讓出執行權
3️⃣ 事件迴圈在等待期間執行其他協程,不會阻塞整個程式
4️⃣ I/O 操作完成後,事件迴圈會繼續執行先前暫停的協程

這種機制能夠讓 Python 應用 同時處理多個 I/O 任務,而不需要多個執行緒或進程

asyncio vs. 傳統同步執行的比較

特性同步程式(Synchronous)asyncio 非同步程式(Asynchronous)
執行方式順序執行,一個任務完成後才能執行下一個允許多個任務同時執行(並發)
適用場景適合 CPU 密集型計算(數學運算、AI 訓練)適合 I/O 密集型應用(API、資料庫存取)
效率每個任務需要等待前一個完成一個任務等待時,其他任務可以繼續執行
記憶體使用量需要開啟多個執行緒或進程,耗費較多記憶體採用單執行緒 + 協程,記憶體消耗較少
並發能力需要 threading 或 multiprocessing透過 asyncio 管理協程,達成高併發

asyncio 的基本概念與關鍵詞

async / await:定義與執行協程

asyncawaitasyncio 的核心關鍵字,用於定義和執行非同步函式。

(1) 定義非同步函式

使用 async def 來定義一個非同步函式(協程):

import asyncio

async def say_hello():
    print("Hello")
    await asyncio.sleep(2)  # 模擬 I/O 操作
    print("World!")

# 建立事件迴圈並執行協程
asyncio.run(say_hello())

🔹 程式解釋:

  1. async def say_hello() 定義了一個非同步函式。
  2. await asyncio.sleep(2) 讓出 CPU 讓其他任務執行,而不會阻塞整個程式。
  3. asyncio.run(say_hello()) 啟動 asyncio 事件迴圈,執行協程。

執行結果(約 2 秒後輸出):

Hello
(等待 2 秒)
World!

事件迴圈(Event Loop)

事件迴圈asyncio 負責管理和執行協程的核心機制,它確保協程在適當的時機運行,而不會阻塞其他任務。

Python 3.7 以上的 asyncio.run() 會自動處理事件迴圈,但有時候我們需要手動控制事件迴圈,例如:

import asyncio

async def task1():
    print("執行任務 1")
    await asyncio.sleep(1)
    print("任務 1 完成")

async def task2():
    print("執行任務 2")
    await asyncio.sleep(2)
    print("任務 2 完成")

async def main():
    loop = asyncio.get_running_loop()  # 獲取當前事件迴圈
    task1_future = loop.create_task(task1())  # 建立任務 1
    task2_future = loop.create_task(task2())  # 建立任務 2
    await task1_future
    await task2_future

asyncio.run(main())

📌 逐行解析

async def main():
    loop = asyncio.get_running_loop()  # 獲取當前事件迴圈

🔹 asyncio.get_running_loop() 取得當前正在運行的 事件迴圈(Event Loop)

🔹 asyncio 的所有非同步任務都是由 事件迴圈管理,我們可以透過這個函式來操作事件迴圈,例如 手動建立非同步任務

    task1_future = loop.create_task(task1())  # 建立任務 1
    task2_future = loop.create_task(task2())  # 建立任務 2

🔹 loop.create_task(task1())

  • task1() 是一個 協程函式(coroutine function),但我們需要將它轉換為 Task 讓事件迴圈管理。
  • loop.create_task()立即安排 task1() 執行,但它不會阻塞 main(),而是允許其他協程並行執行。
  • task1_futuretask2_future 都是 asyncio.Task 物件,表示它們正在背景執行。
    await task1_future
    await task2_future

🔹 await task1_future

  • await 會等待 task1() 完成,然後繼續執行 task2()
  • task1()task2() 是並行運行的(因為它們是 Task,並且 loop.create_task() 已經安排它們執行)。
  • await task1_future 不會影響 task2() 的執行,它只是確保 task1() 完成後才繼續執行 main() 後續的程式碼
  • 類似地,await task2_future 會確保 task2() 也執行完畢

📌 這段程式碼的完整流程

1️⃣ asyncio.get_running_loop() 取得 當前事件迴圈
2️⃣ 透過 loop.create_task(task1())loop.create_task(task2()),將 兩個任務添加到事件迴圈
3️⃣ task1()task2()同時執行,因為它們是獨立的 Task
4️⃣ await task1_future 會等待 task1() 完成,但不會影響 task2()
5️⃣ await task2_future 會等待 task2() 完成,確保 main() 不會過早結束。

執行結果(約 2 秒完成):

執行任務 1
執行任務 2
1 秒後)
任務 1 完成
(再 1 秒後)
任務 2 完成

📌 create_task() vs await 的區別

如果我們 不用 create_task(),而是直接 await 兩個函式,它們會順序執行,而不是並行執行

async def main():
    await task1()  # 先執行 task1(),完成後才執行 task2()
    await task2()  # 等 task1() 結束後才開始執行 task2()

這樣 task2() 會等 task1() 執行完後才開始,因此 無法達到並行執行的效果

而使用 create_task(),則兩個任務可以同時運行

async def main():
    task1_future = asyncio.create_task(task1())  # 立即開始 task1
    task2_future = asyncio.create_task(task2())  # 立即開始 task2
    await task1_future  # 確保 task1 完成
    await task2_future  # 確保 task2 完成

這樣 task1()task2() 會同時執行,讓程式更有效率。


並行執行多個協程 (asyncio.gather)

如果我們有多個非同步函式,希望它們可以同時執行,可以使用 asyncio.gather()

import asyncio

async def task(name, seconds):
    print(f"開始 {name}")
    await asyncio.sleep(seconds)
    print(f"{name} 完成")

async def main():
    await asyncio.gather(
        task("任務 A", 2),
        task("任務 B", 1),
        task("任務 C", 3)
    )

asyncio.run(main())

這段程式碼的作用是 透過 asyncio 來並行執行多個非同步任務,避免同步執行時的等待時間。以下是程式碼的詳細解析:

📌 程式碼解析

import asyncio  # 匯入 asyncio 模組,負責非同步執行
1️⃣ 定義一個非同步任務 task()
async def task(name, seconds):
    print(f"開始 {name}")
    await asyncio.sleep(seconds)  # 模擬 I/O 等待 (不會阻塞其他協程)
    print(f"{name} 完成")

🔹 說明

  • 這是一個非同步函式,使用 async def 來定義。
  • await asyncio.sleep(seconds) 模擬一個 I/O 操作(例如 API 請求、資料庫查詢等)。
  • await 代表「暫停當前任務,允許其他任務繼續執行」,不會阻塞整個應用。
2️⃣ main():並行執行多個任務
async def main():
    await asyncio.gather(
        task("任務 A", 2),
        task("任務 B", 1),
        task("任務 C", 3)
    )

🔹 說明

  • asyncio.gather() 可以同時執行多個協程(Coroutine),達成並發執行
  • await asyncio.gather(...) 會等待所有任務 同時開始執行,並在 全部執行完成後 才返回結果。
  • task("任務 A", 2)task("任務 B", 1)task("任務 C", 3) 會同時啟動,不會彼此等待!

3️⃣ 啟動事件迴圈

asyncio.run(main())  

🔹 說明

  • asyncio.run(main()) 會自動建立事件迴圈,並執行 main() 協程
  • Python 3.7 以上推薦用 asyncio.run() 來啟動非同步程式

📌 執行流程與結果分析

1️⃣ asyncio.gather() 啟動 task("A", 2)task("B", 1)task("C", 3)
2️⃣ 這三個任務同時開始,不會互相等待
3️⃣ 等到 task("B", 1) 先完成,然後 task("A", 2),最後 task("C", 3)

執行結果:

開始 任務 A
開始 任務 B
開始 任務 C
(1 秒後)
任務 B 完成
(1 秒後)
任務 A 完成
(1 秒後)
任務 C 完成

📌 與同步執行的對比

如果改為同步執行,則所有任務都會依序等待前一個任務完成:

import time

def task(name, seconds):
    print(f"開始 {name}")
    time.sleep(seconds)  # 阻塞程式
    print(f"{name} 完成")

def main():
    task("任務 A", 2)
    task("任務 B", 1)
    task("任務 C", 3)

main()

🔹 同步執行的結果:

開始 任務 A
(2 秒後)
任務 A 完成
開始 任務 B
(1 秒後)
任務 B 完成
開始 任務 C
(3 秒後)
任務 C 完成

總共耗時:2 + 1 + 3 = 6 秒


asyncio 的應用場景

asyncio 適用於 I/O 密集型任務,例如:

1️⃣ 網路請求(HTTP API 呼叫)
使用 aiohttp 搭配 asyncio 來並發執行多個 API 請求。

2️⃣ 資料庫查詢
使用 aiomysqlasyncpg 等非同步資料庫庫來提高查詢效率。

3️⃣ 檔案 I/O 操作
使用 aiofiles 進行非同步讀寫,提高檔案處理速度。

4️⃣ 爬蟲與批量處理
在網路爬蟲場景中,asyncio 可讓多個爬蟲任務同時運行,提高爬取速度。


結論

asyncio 是 Python 內建的非同步程式設計庫,主要透過 協程、事件迴圈、異步 I/O 來提高應用效能,特別適用於 I/O 密集型應用。

如果你的應用場景涉及大量等待操作(如 API 請求、資料庫存取等),那麼學習 asyncio 並將其應用到專案中,將能顯著提升效能與並發能力! 🚀

Similar Posts