3.1 模組 asyncio
自己為非同步子任務創建執行緒早就沒人這麼做了。嗯,也不是不能創建,但這樣的操作被認為是太低階的東西,只有框架開發者會用它,還是不得不用的時候。
現在流行的是
非同步編程,async/await
操作符
和 coroutines 跟 tasks。先按部就班地來聊聊...
一點歷史背景
一開始在 Python 中,非同步編程的任務是用基於生成器的 coroutines 來解決的。
然後,在 Python 3.4 中,引入了
asyncio
模組(有時候也寫成 async IO
),裡面實現了非同步編程的機制。
在 Python 3.5 中又出現了 async/await
的構造。
現在給你一點入門資訊。我會先簡單講一下這些東西,然後會詳細再詳細。因為這些東西幾乎都互相關聯,要想詳細解釋其中之一都要提到另一些。
模組 asyncio
模組 asyncio
是為撰寫非同步程序而設計的,能提供並行執行任務的能力。
它支援非同步 I/O 操作、計時器、套接字、執行 coroutines 和多執行緒,可以在一個或多個事件循環中工作。
Coroutines
Coroutines 是
非同步函數,用關鍵字 async def
定義。
Coroutines 讓你可以用
關鍵字 await 中止執行
,
這樣其他的 coroutines 就可以在這段時間執行
。
import asyncio
# 定義非同步函數(coroutine)
async def main():
print('Hello ...')
# 中止執行 1 秒
await asyncio.sleep(1)
print('... World!')
# 在事件循環中啟動非同步函數 main()
asyncio.run(main())
事件循環 (Event Loop)
事件循環管理 coroutines、tasks 和其他非同步操作的執行。調用 asyncio.run()
啟動事件循環,
並執行 coroutine 直至完成。
import asyncio
async def main():
print('Hello ...')
await asyncio.sleep(1)
print('... World!')
# 獲取當前事件循環
loop = asyncio.get_event_loop()
# 執行 coroutine 直至完成
loop.run_until_complete(main())
# 完成所有任務後關閉事件循環
loop.close()
Tasks
Tasks 讓你可以並行地執行 coroutines。可以用 asyncio.create_task()
或
asyncio.ensure_future()
創建。
import asyncio
# 定義一個將延遲執行的 coroutine
async def say_after(delay, what):
# 延遲執行指定時間
await asyncio.sleep(delay)
print(what)
# 主要 coroutine
async def main():
# 創建 tasks 並行執行 coroutines
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'world'))
# 等待兩個 tasks 完成
await task1
await task2
# 啟動主要 coroutine
asyncio.run(main())
Futures
Future
對象表示將在未來可用的非同步操作結果。
例如,它們用於等待非同步任務的完成。
import asyncio
# 定義一個模擬長時間任務的 coroutine
async def long_running_task():
print('Task started')
# 暫停執行 3 秒
await asyncio.sleep(3)
print('Task finished')
return 'Result'
# 主要 coroutine
async def main():
# 創建 future 以等待任務完成
future = asyncio.ensure_future(long_running_task())
# 等待任務完成並獲取結果
result = await future
print(f'Task result: {result}')
# 啟動主要 coroutine
asyncio.run(main())
3.2 非同步函數 — async def
宣告非同步函數的方法跟宣告普通函數一樣,只是在關鍵字 def
之前加上
async
。
async def 函數名稱(參數):
函數代碼
非同步函數的宣告就跟普通函數一樣,調用也一樣,但它返回的結果卻不同。如果調用非同步函數,它不會返回結果,而是返回一個特殊的對象—— coroutine。
甚至可以這樣檢查:
import asyncio
async def main():
print("Hello World")
# 調用非同步函數,返回 coroutine
result = main()
# 檢查結果類型
print(type(result)) # <class 'coroutine'>
發生了什麼事情?當你用 async 標記函數時,其實就是在它上面加上一個裝飾器,大概是這樣的:
def async_decorator(func):
# 創建 Task 對象
task = Task()
# 將 func 傳入 task 以便執行
task.target = func
# 將 task 加入事件循環的任務隊列
eventloop.add_task(task)
# 返回 task 對象
return task
你的代碼就像:
import asyncio
@async_decorator
def main():
print("Hello World")
result = main()
print(type(result)) # <class 'coroutine'>
這個類比的意義在於:
當你調用非同步函數時,創建了一個特殊的 Task
對象,它將在未來的某個時候執行你的函數。可能是 0.0001 秒後,也可能是 10 秒後。
這個 task
對象立即作為你非同步函數調用的結果返回給你。這就是 coroutine。
你的非同步函數可能還沒開始執行,而 task
(coroutine)對象你已經有了。
為什麼要這個 task
(coroutine)呢?你能做的事不多,但有三件事可以做:
- 等待非同步函數的執行完成。
- 等待非同步函數執行結束並從 coroutine 獲取函數的執行結果。
- 等待 10 個(任意數目)非同步函數的執行完成。
如何做到這些,我下面會講。
3.3 操作符 await
大部分 coroutine 的操作都從「等待非同步函數的執行」開始。因此我們有一個特殊的操作符 await
。
你只要在 coroutine 前寫上它:
await coroutine
或者直接在調用非同步函數時寫上:
await 非同步函數(參數)
當 Python 在代碼中遇到 操作符 await
,它會暫停當前函數的執行並等待,直到 coroutine 執行完畢——直到非同步函數完成。
重要!
操作符 await
只能用於 非同步函數內 以暫停執行,直到完成另一個 coroutine 或非同步操作。
這麼做是為了簡化在非同步函數調用間的切換。這種 await 調用實際上是一種聲明「我們這裡等著,不知道要等多久——去執行其他非同步函數吧」。
例子:
import asyncio
# 定義非同步函數
async def async_print(text):
print(text)
# 主要非同步函數
async def main():
# 使用 await 等待非同步函數的執行
await async_print("Hello World")
# 啟動主事件循環並執行 coroutine main()
asyncio.run(main()) # 啟動非同步函數
實際上,操作符 await
更聰明——它還返回非同步函數執行的結果。
例子:
import asyncio
# 定義一個將兩個數字相加的非同步函數
async def async_add(a, b):
return a + b
# 主要非同步函數
async def main():
# 使用 await 獲取 async_add 的執行結果
sum = await async_add(100, 200)
print(sum)
# 啟動主事件循環並執行 coroutine main()
asyncio.run(main()) # 啟動非同步函數
總結一下,操作符 await
:
- 暫停當前非同步函數,直到另一個 coroutine 或非同步操作完成。
- 返回非同步操作或 coroutine 的執行結果。
- 只能在非同步函數內使用。
GO TO FULL VERSION