看起來兩者是一樣的,因為都可以用以下的語法來異步獲取結果,
1
2
|
result = await future result = await coroutine |
實際上,coroutine 是生成器函數,它既可以從外部接受參數,也可以產生結果。使用 coroutine 的好處是,我們可以暫停一個函數,然后稍后恢復執行。比如在涉及到網路操作的情況下,能夠停下函數直到響應到來。在停下的這段時間內,我們可以切換到其他任務繼續執行。
而 Future 更像是 Javascript 中的 Promise 對象。它是一個占位符,其值會在將來被計算出來。在上述的例子中,當我們在等待網絡 IO 函數完成時,函數會給我們一個容器,Promise 會在完成時填充該容器。填充完畢后,我們可以用回調函數來獲取實際結果。
Task 對象是 Future 的子類,它將 coroutine 和 Future 聯系在一起,將 coroutine 封裝成一個 Future 對象。
一般會看到兩種任務啟動方法,
1
2
3
4
5
|
asyncio.ensure_future(func1()), asyncio.ensure_future(func2()) ) loop.run_until_complete(tasks) |
和
1
2
3
4
5
|
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 對象,
1
2
3
4
5
|
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。
故結束任務可以用
1
2
|
for task in asyncio.Task.all_tasks(): task.cancel() |
這種方法將所有任務找出并 cancel。
但 CTRL-C 也會將事件循環停止,所以有必要重啟事件循環,
1
2
3
4
5
6
7
8
|
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)
將異常轉換為正常的結果返回。