asyncio 的 coroutine物件 與 Future物件使用指南

NO IMAGE

coroutine 與 Future 的關係

看起來兩者是一樣的,因為都可以用以下的語法來非同步獲取結果,


result = await future
result = await coroutine

實際上,coroutine 是生成器函式,它既可以從外部接受引數,也可以產生結果。使用 coroutine 的好處是,我們可以暫停一個函式,然後稍後恢復執行。比如在涉及到網路操作的情況下,能夠停下函式直到響應到來。在停下的這段時間內,我們可以切換到其他任務繼續執行。

而 Future 更像是 Javascript 中的 Promise 物件。它是一個佔位符,其值會在將來被計算出來。在上述的例子中,當我們在等待網路 IO 函式完成時,函式會給我們一個容器,Promise 會在完成時填充該容器。填充完畢後,我們可以用回撥函式來獲取實際結果。

Task 物件是 Future 的子類,它將 coroutine 和 Future 聯絡在一起,將 coroutine 封裝成一個 Future 物件。

一般會看到兩種任務啟動方法,


tasks = asyncio.gather(
asyncio.ensure_future(func1()),
asyncio.ensure_future(func2())
)
loop.run_until_complete(tasks)


tasks = [
asyncio.ensure_future(func1()),
asyncio.ensure_future(func2())
]
loop.run_until_complete(asyncio.wait(tasks))

ensure_future 可以將 coroutine 封裝成 Task。asyncio.gather 將一些 Future 和 coroutine 封裝成一個 Future。

asyncio.wait 則本身就是 coroutine。

run_until_complete 既可以接收 Future 物件,也可以是 coroutine 物件,


BaseEventLoop.run_until_complete(future)
Run until the Future is done.
If the argument is a coroutine object, it is wrapped by ensure_future().
Return the Future's result, or raise its exception.

Task 任務的正確退出方式

在 asyncio 的任務迴圈中,如果使用 CTRL-C 退出的話,即使捕獲了異常,Event Loop 中的任務會報錯,出現如下的錯誤,

Task was destroyed but it is pending!
task: <Task pending coro=<kill_me() done, defined at test.py:5> wait_for=<Future pending cb=[Task._wakeup()]>>

根據官方文件,Task 物件只有在以下幾種情況,會認為是退出,

a result / exception are available, or that the future was cancelled

Task 物件的 cancel 和其父類 Future 略有不同。當呼叫 Task.cancel() 後,對應 coroutine 會在事件迴圈的下一輪中丟擲 CancelledError 異常。使用 Future.cancelled() 並不能立即返回 True(用來表示任務結束),只有在上述異常被處理任務結束後才算是 cancelled。

故結束任務可以用


for task in asyncio.Task.all_tasks():
task.cancel()

這種方法將所有任務找出並 cancel。

但 CTRL-C 也會將事件迴圈停止,所以有必要重啟事件迴圈,


try:
loop.run_until_complete(tasks)
except KeyboardInterrupt as e:
for task in asyncio.Task.all_tasks():
task.cancel()
loop.run_forever() # restart loop
finally:
loop.close()

在每個 Task 中捕獲異常是必要的,如果不確定,可以使用

asyncio.gather(..., return_exceptions=True)

將異常轉換為正常的結果返回。

您可能感興趣的文章:

Python中使用asyncio 封裝檔案讀寫探索Python3.4中新引入的asyncio模組在Python3中使用asyncio庫進行快速資料抓取的教程基於asyncio 非同步協程框架實現收集B站直播彈幕