一文速通 Python 并行計算:14 Python 異步編程-協程的管理和調度
摘要:
Python 異步編程基于 async/await 構建協程,運行在事件循環中。協程生成 Task,遇到?await?時掛起,I/O 完成觸發回調恢復運行,通過事件循環非阻塞調度并發任務,實現單線程高并發。
關于我們更多介紹可以查看云文檔:Freak 嵌入式工作室云文檔,或者訪問我們的 wiki:****https://github.com/leezisheng/Doc/wik
原文鏈接:
FreakStudio的博客
往期推薦:
學嵌入式的你,還不會面向對象??!
全網最適合入門的面向對象編程教程:00 面向對象設計方法導論
全網最適合入門的面向對象編程教程:01 面向對象編程的基本概念
全網最適合入門的面向對象編程教程:02 類和對象的 Python 實現-使用 Python 創建類
全網最適合入門的面向對象編程教程:03 類和對象的 Python 實現-為自定義類添加屬性
全網最適合入門的面向對象編程教程:04 類和對象的Python實現-為自定義類添加方法
全網最適合入門的面向對象編程教程:05 類和對象的Python實現-PyCharm代碼標簽
全網最適合入門的面向對象編程教程:06 類和對象的Python實現-自定義類的數據封裝
全網最適合入門的面向對象編程教程:07 類和對象的Python實現-類型注解
全網最適合入門的面向對象編程教程:08 類和對象的Python實現-@property裝飾器
全網最適合入門的面向對象編程教程:09 類和對象的Python實現-類之間的關系
全網最適合入門的面向對象編程教程:10 類和對象的Python實現-類的繼承和里氏替換原則
全網最適合入門的面向對象編程教程:11 類和對象的Python實現-子類調用父類方法
全網最適合入門的面向對象編程教程:12 類和對象的Python實現-Python使用logging模塊輸出程序運行日志
全網最適合入門的面向對象編程教程:13 類和對象的Python實現-可視化閱讀代碼神器Sourcetrail的安裝使用
全網最適合入門的面向對象編程教程:全網最適合入門的面向對象編程教程:14 類和對象的Python實現-類的靜態方法和類方法
全網最適合入門的面向對象編程教程:15 類和對象的 Python 實現-__slots__魔法方法
全網最適合入門的面向對象編程教程:16 類和對象的Python實現-多態、方法重寫與開閉原則
全網最適合入門的面向對象編程教程:17 類和對象的Python實現-鴨子類型與“file-like object“
全網最適合入門的面向對象編程教程:18 類和對象的Python實現-多重繼承與PyQtGraph串口數據繪制曲線圖
全網最適合入門的面向對象編程教程:19 類和對象的 Python 實現-使用 PyCharm 自動生成文件注釋和函數注釋
全網最適合入門的面向對象編程教程:20 類和對象的Python實現-組合關系的實現與CSV文件保存
全網最適合入門的面向對象編程教程:21 類和對象的Python實現-多文件的組織:模塊module和包package
全網最適合入門的面向對象編程教程:22 類和對象的Python實現-異常和語法錯誤
全網最適合入門的面向對象編程教程:23 類和對象的Python實現-拋出異常
全網最適合入門的面向對象編程教程:24 類和對象的Python實現-異常的捕獲與處理
全網最適合入門的面向對象編程教程:25 類和對象的Python實現-Python判斷輸入數據類型
全網最適合入門的面向對象編程教程:26 類和對象的Python實現-上下文管理器和with語句
全網最適合入門的面向對象編程教程:27 類和對象的Python實現-Python中異常層級與自定義異常類的實現
全網最適合入門的面向對象編程教程:28 類和對象的Python實現-Python編程原則、哲學和規范大匯總
全網最適合入門的面向對象編程教程:29 類和對象的Python實現-斷言與防御性編程和help函數的使用
全網最適合入門的面向對象編程教程:30 Python的內置數據類型-object根類
全網最適合入門的面向對象編程教程:31 Python的內置數據類型-對象Object和類型Type
全網最適合入門的面向對象編程教程:32 Python的內置數據類型-類Class和實例Instance
全網最適合入門的面向對象編程教程:33 Python的內置數據類型-對象Object和類型Type的關系
全網最適合入門的面向對象編程教程:34 Python的內置數據類型-Python常用復合數據類型:元組和命名元組
全網最適合入門的面向對象編程教程:35 Python的內置數據類型-文檔字符串和__doc__屬性
全網最適合入門的面向對象編程教程:36 Python的內置數據類型-字典
全網最適合入門的面向對象編程教程:37 Python常用復合數據類型-列表和列表推導式
全網最適合入門的面向對象編程教程:38 Python常用復合數據類型-使用列表實現堆棧、隊列和雙端隊列
全網最適合入門的面向對象編程教程:39 Python常用復合數據類型-集合
全網最適合入門的面向對象編程教程:40 Python常用復合數據類型-枚舉和enum模塊的使用
全網最適合入門的面向對象編程教程:41 Python常用復合數據類型-隊列(FIFO、LIFO、優先級隊列、雙端隊列和環形隊列)
全網最適合入門的面向對象編程教程:42 Python常用復合數據類型-collections容器數據類型
全網最適合入門的面向對象編程教程:43 Python常用復合數據類型-擴展內置數據類型
全網最適合入門的面向對象編程教程:44 Python內置函數與魔法方法-重寫內置類型的魔法方法
全網最適合入門的面向對象編程教程:45 Python實現常見數據結構-鏈表、樹、哈希表、圖和堆
全網最適合入門的面向對象編程教程:46 Python函數方法與接口-函數與事件驅動框架
全網最適合入門的面向對象編程教程:47 Python函數方法與接口-回調函數Callback
全網最適合入門的面向對象編程教程:48 Python函數方法與接口-位置參數、默認參數、可變參數和關鍵字參數
全網最適合入門的面向對象編程教程:49 Python函數方法與接口-函數與方法的區別和lamda匿名函數
全網最適合入門的面向對象編程教程:50 Python函數方法與接口-接口和抽象基類
全網最適合入門的面向對象編程教程:51 Python函數方法與接口-使用Zope實現接口
全網最適合入門的面向對象編程教程:52 Python函數方法與接口-Protocol協議與接口
全網最適合入門的面向對象編程教程:53 Python字符串與序列化-字符串與字符編碼
全網最適合入門的面向對象編程教程:54 Python字符串與序列化-字符串格式化與format方法
全網最適合入門的面向對象編程教程:55 Python字符串與序列化-字節序列類型和可變字節字符串
全網最適合入門的面向對象編程教程:56 Python字符串與序列化-正則表達式和re模塊應用
全網最適合入門的面向對象編程教程:57 Python字符串與序列化-序列化與反序列化
全網最適合入門的面向對象編程教程:58 Python字符串與序列化-序列化Web對象的定義與實現
全網最適合入門的面向對象編程教程:59 Python并行與并發-并行與并發和線程與進程
一文速通Python并行計算:00 并行計算的基本概念
一文速通Python并行計算:01 Python多線程編程-基本概念、切換流程、GIL鎖機制和生產者與消費者模型
一文速通Python并行計算:02 Python多線程編程-threading模塊、線程的創建和查詢與守護線程
一文速通Python并行計算:03 Python多線程編程-多線程同步(上)—基于互斥鎖、遞歸鎖和信號量
一文速通Python并行計算:04 Python多線程編程-多線程同步(下)—基于條件變量、事件和屏障
一文速通Python并行計算:05 Python多線程編程-線程的定時運行
一文速通Python并行計算:06 Python多線程編程-基于隊列進行通信
一文速通Python并行計算:07 Python多線程編程-線程池的使用和多線程的性能評估
一文速通Python并行計算:08 Python多進程編程-進程的創建命名、獲取ID、守護進程的創建和終止進程
一文速通Python并行計算:09 Python多進程編程-進程之間的數據同步-基于互斥鎖、遞歸鎖、信號量、條件變量、事件和屏障
一文速通Python并行計算:10 Python多進程編程-進程之間的數據共享-基于共享內存和數據管理器
一文速通Python并行計算:11 Python多進程編程-進程之間的數據安全傳輸-基于隊列和管道
一文速通Python并行計算:12 Python多進程編程-進程池Pool
一文速通Python并行計算:13 Python異步編程-基本概念與事件循環和回調機制
更多精彩內容可看:
C語言一點五編程實戰:純 C 的模塊化×繼承×多態框架
給你的 Python 加加速:一文速通 Python 并行計算
一文搞懂 CM3 單片機調試原理
肝了半個月,嵌入式技術棧大匯總出爐
電子計算機類比賽的“武林秘籍”
一個MicroPython的開源項目集錦:awesome-micropython,包含各個方面的Micropython工具庫
Avnet ZUBoard 1CG開發板—深度學習新選擇
工程師不要迷信開源代碼,還要注重基本功
什么?配色個性化的電機驅動模塊?!!
什么?XIAO主控新出三款擴展板!
手把手教你實現Arduino發布第三方庫
萬字長文手把手教你實現MicroPython/Python發布第三方庫
一文速通電子設計大賽,電子人必看的獲獎秘籍
一文速通光電設計大賽,電子人必看!
工科比賽“無腦”操作指南:知識學習硬件選購→代碼調試→報告撰寫的保姆級路線圖
單場會議拍攝收費6000+?拍攝技巧和步驟都在這里
0基礎如何沖擊大唐杯國獎?學姐的的備賽心得都在這里
爆肝整理長文】大學生競賽速通指南:選題 × 組隊 × 路演 48 小時備賽搞定
當代大學生競賽亂象:從“內卷”到“祖傳項目”的生存指南
女大學生擺攤虧損5000元踩點實錄:成都哪里人最多、最容易賺到錢?我告訴你!
用拍立得在成都網紅打卡點賺錢:一份超實用地攤級旅游副業教程
成都印象:一個電子女孩親手做了點浪漫
普通繼電器 vs 磁保持繼電器 vs MOS管:工作原理與電路設計全解析
告別TP4056!國產SY3501D單芯片搞定充放電+升壓,僅需7個元件!附開源PCB文件
POB面向老板編程—現實驅動的新型編程范式
文檔獲取:
可訪問如下鏈接進行對文檔下載:
https://github.com/leezisheng/Doc
該文檔是一份關于 并行計算 和 Python 并發編程 的學習指南,內容涵蓋了并行計算的基本概念、Python 多線程編程、多進程編程以及協程編程的核心知識點:
正文
在上文提到的例子中,我們看到當一個程序變得很大而且復雜時,將其劃分為子程序,每一部分實現特定的任務是個不錯的方案。子程序不能單獨執行,只能在主程序的請求下執行,主程序負責協調使用各個子程序。協程就是子程序的泛化。和子程序一樣的事,協程只負責計算任務的一步;和子程序不一樣的是,協程沒有主程序來進行調度。這是因為協程通過管道連接在一起,沒有監視函數負責順序調用它們。
對于子程序來說,調用是通過棧實現的,子程序調用總是一個入口,一次返回,調用順序是明確的。比如 A 調用 B,B 在執行過程中又調用了 C,C 執行完畢返回,B 執行完畢返回,最后是 A 執行完畢。協程看上去也是子程序,但執行過程中,在子程序內部可中斷,然后轉而執行別的子程序,在適當的時候再返回來接著執行。從心理學的角度看,協程類似于人類的“任務切換”能力,我們可以暫時將注意力從一個任務轉移到另一個,然后再回來繼續未完成的任務。
或者說,**在協程中,執行點可以被掛起,可以被從之前掛起的點恢復執行。**通過協程池就可以插入到計算中:運行第一個任務,直到它返回(yield)執行權,然后運行下一個,這樣順著執行下去。這種插入的控制組件就是前文介紹的事件循環。它持續追蹤所有的協程并執行它們。
協程的另外一些重要特性如下:
- 協程可以有多個入口點,并可以 yield 多次;
- 協程可以將執行權交給其他協程
yield 表示協程在此暫停,并且將執行權交給其他協程。因為協程可以將值與控制權一起傳遞給另一個協程,所以“yield 一個值”就表示將值傳給下一個執行的協程。
協程與傳統的線程或進程相比,有幾個關鍵區別:
**(1)輕量級:**協程通常是用戶態的,子程序切換(函數)不是線程切換,由程序自身控制,切換開銷比系統線程小得多。
**(2)非搶占式:**協程的切換是協作式的,即協程需要顯式地 yield 來讓出控制權。
**(3)更好的控制:**協程提供了更細粒度的控制,如何以及何時切換是由程序員或協程庫決定的。
協程可以處理 IO 密集型程序的效率問題,但是處理 CPU 密集型不是它的長處,如要充分發揮 CPU 利用率可以結合多進程 + 協程。
1.使用 Asyncio 管理協程
Python3.x 提供了如下方式實現協程:
asyncio + yield
from (python3.4+) :
asyncio
是 Python3.4 版本引入的標準庫,直接內置了對異步 IO 的支持。asyncio
的異步操作,需要在 coroutine
中通過 yield from
完成。
import asyncio@asyncio.coroutine
def test(i):print('test_1', i)r = yield from asyncio.sleep(1)print('test_2', i)if __name__ == '__main__':loop = asyncio.get_event_loop()tasks = [test(i) for i in range(3)]loop.run_until_complete(asyncio.wait(tasks))loop.close()
asyncio.coroutine
使用裝飾器,定義了一個協程。所謂裝飾器是給現有的模塊增添新的小功能的函數,它可以對原函數進行功能擴展,而且還不需要修改原函數的內容,也不需要修改原函數的調用。
使用 @asyncio.coroutine
定義協程的通用方法如下:
import asyncio@asyncio.coroutine
def coroutine_function(function_arguments):_# DO_SOMETHING_
以上代碼將 test(i)
定義為一個協程,然后就把這個協程放到事件循環中執行。test(i)
首先執行打印操作,這里用 asyncio.sleep(1)
模擬一個耗時 1 秒的 IO 操作,asyncio.sleep()
本身也是一個協程,這里使用 yield from
語法調用 asyncio.sleep()
,但注意線程不會等待 asyncio.sleep()
,而是直接中斷并執行下一個消息循環。當 asyncio.sleep()
返回時,線程就可以從 yield from
拿到返回值(此處是 None
),然后接著執行下一行語句。由此實現異步執行。
asyncio + async/await
(python3.5+):
為了簡化并更好地標識異步 IO,從 Python3.5 開始引入了新的語法 async 和 await,可以讓 coroutine 的代碼更簡潔易讀。請注意,async 和 await 是 coroutine 的新語法,使用新語法只需要做兩步簡單的替換:
- 把 @asyncio.coroutine 替換為 async;
- 把 yield from 替換為 await,即讓出當前的執行權,等待的對象有結果返回時,再重新獲得可以被繼續執行的權利,只有可等待對象才能被 await。
注意,包含 @asyncio.coroutine 裝飾器的將從 Python3.11 中刪除,因此 asyncio 模塊沒有 @asyncio.coroutine 裝飾符。
import asyncio
async def test(i):print('test_1', i)await asyncio.sleep(1)print('test_2', i)if __name__ == '__main__':loop = asyncio.new_event_loop()asyncio.set_event_loop(loop)_# 在python3.8后直接把協程對象傳給asyncio.wait()是不行的,__# 必須封裝成tasks對象傳入_tasks = [loop.create_task(test(1)),loop.create_task(test(2)),loop.create_task(test(3))]loop.run_until_complete(asyncio.wait(tasks))loop.close()
除了 await
方法,asyncio
提供了**asyncio.run()**來執行協程:
run()
函數接收一個協程對象,在執行時,總會**創建一個新的事件循環,并在結束后關閉循環。****理想情況下,****run() ****函數應當被作為程序的總入口,并且只會被調用一次。**如果同一線程中還有其它事件循環在運行,則此方法不能被調用。
2.使用 Asyncio 控制任務
Asyncio
是用來處理事件循環中的異步進程和并發任務執行的。**它還提供了 asyncio.Task() 類,可以在任務中使用協程。**它的作用是,在同一事件循環中,**運行某一個任務的同時可以并發地運行多個任務。當協程被包在任務中,它會自動將任務和事件循環連接起來,當事件循環啟動的時候,任務自動運行。**這樣就提供了一個可以自動驅動協程的機制。
2.1 asyncio.create_task()
下面的例子中我們使用 asyncio.create_task()
創建 Task,create_task()
會把一個協程打包成一個任務(Task
),并立即排入日程準備執行,函數返回值是打包完成的 Task
對象。
import asyncio
import time
async def foo(n):await asyncio.sleep(n)async def main():task1 = asyncio.create_task(foo(1))task2 = asyncio.create_task(foo(2))t1 = time.time()print('hello')await task1await task2print('coroutine')t2 = time.time()print('cost:', t2 - t1)asyncio.run(main())
如下為運行結果:
當使用 create_task()
時,創建的任務立即被加入到事件循環中,并不會阻塞當前的程序,所以上述程序在打印出 hello
后只需等待 2 秒就打印出 coroutine
。
2.2 asyncio.gather()
asyncio.gather()
函數允許調用者將多個可等待對象組合在一起。分組后,可等待對象可以并發執行、等待和取消。它是一個有用的實用函數,可用于分組和執行多個協程或多個任務。
從功能上看,asyncio.wait
和 asyncio.gather
實現的效果是相同的,都是把所有 Task
任務結果收集起來。但不同的是,asyncio.wait
會返回兩個值:done
和 pending
,done
為已完成的協程 Task
,pending
為超時未完成的協程 Task
,需通過 future.result
調用 Task
的 result
;而 asyncio.gather返回的是所有已完成Task的result**,不需要再進行調用或其他操作,就可以得到全部結果。**
import asyncioasync def foo():return 'foo'
async def bar():raise RuntimeError('fake runtime error')
async def main():task1 = asyncio.create_task(foo())task2 = asyncio.create_task(bar())_# return_exceptions=True__# 如果return_exceptions為True,__# 異常會和成功的結果一樣處理,并聚合至結果列表_results = await asyncio.gather(task1, task2, return_exceptions=True)print(results)_# 返回結果的順序和傳參順序一致__# isinstance() 函數來判斷一個對象是否是一個已知的類型__# isinstance(object, classinfo)_assert isinstance(results[1], RuntimeError)try:_# 如果return_exceptions為False(默認)__# 所引發的首個異常會立即傳播給等待gather()的任務_results = await asyncio.gather(task1, task2, return_exceptions=False)_# 此處打印并不會被執行, results 也未被賦值_print(results)except RuntimeError as runtime_err:_# 捕獲異常并打印: fake runtime error_print(runtime_err)asyncio.run(main())
執行結果如下:
2.3 asyncio.wait()
asyncio.wait()
函數可用于等待一組異步任務完成,回想一下,asyncio
任務是包裝協程的 asyncio.Task
類的一個實例。它允許獨立調度和執行協程,Task
實例提供任務句柄以查詢狀態和獲取結果。**wait()**函數允許我們等待一組任務完成。等待調用可以配置為等待不同的條件,例如所有任務完成、第一個任務完成以及第一個任務因錯誤而失敗。
asyncio.wait
最常見的寫法是:await asyncio.wait(task_list)
,表示運行直到所有給定的協程都完成。常見寫法為:
...
_# create many tasks_
tasks = [asyncio.create_task(task_coro(i)) for i in range(10)]
示例代碼如下:
import asyncioasync def foo():await asyncio.sleep(3)return 'foo'
async def bar():await asyncio.sleep(1)return 'bar'
async def main():task1 = asyncio.create_task(foo())task2 = asyncio.create_task(bar())_# 有一個任務執行完成即返回, 總共耗時 1 秒_done, pending = await asyncio.gather(task1, task2, return_exceptions=True)_# done 集合里包含打包成 Task 的 bar()_print(f'done: {done}')_# pendding 集合里包含打包成 Task 的 foo()_print(f'pending: {pending}')_# 所有任務執行完成后返回, 總共耗時 3 秒_done, pending = await asyncio.gather(task1, task2, return_exceptions=True)_# done 集合里包含被帶打包成 Task 的 foo() 和 bar()_print(f'done: {done}')_# pending 集合為空_print(f'pending: {pending}')_# 所有任務執行完成, 但運行時間不能超 2 秒后返回, 總共耗時 2 秒_done, pending = await asyncio.gather(task1, task2, return_exceptions=True)_# done 集合里包含打包成 Task 的 bar()_print(f'done: {done}')_# pendding 集合里包含打包成 Task 的 foo()_print(f'pending: {pending}')
asyncio.run(main())
2.4 asyncio.as_completed()
有時,我們必須在完成一個后臺任務后立即開始下面的動作。比如我們爬取一些數據,馬上調用機器學習模型進行計算,gather
方法不能滿足我們的需求,但是我們可以使用 as_completed
方法。
as_completed 不是并發方法,接受 aws 集合,返回一個帶有 yield 語句的迭代器。所以我們可以直接遍歷每個完成的后臺任務,我們可以對每個任務單獨處理異常,而不影響其他任務的執行:
示例代碼如下:
import asyncioasync def foo():await asyncio.sleep(2)return 'foo'async def bar():await asyncio.sleep(1)return 'bar'async def main():for fut in asyncio.as_completed({foo(), bar()}):earliest_result = await fut_# 會依次打印 bar 和 foo, 因為 bar() 會更早執行完畢_print(earliest_result)asyncio.run(main())
以上介紹多任務并發時引入了超時的概念,超時也可以被應用在單獨的一個任務中,使用 asyncio.wait_for(aw, timeout)
函數,該函數接受一個任務 aw
和超時時間 timeout
,如果在限制時間內完成,則會正常返回,否則會被取消并拋出 asyncio.TimeoutError
異常。
為了防止任務被取消,可以使用 asyncio.shield(aw)
進行保護。shield()
會屏蔽外部取消操作,如果外部任務被取消,其內部正在執行的任務不會被取消,在內部看來取消操作并沒有發生,由于內部仍正常執行,執行完畢后會觸發異常,如果確保程序能忽略異常繼續執行,需要在外部使用 try-except
捕獲異常。如果在任務內部取消,則會被成功取消。
Asyncio
模塊為我們提供了 asyncio.Task(coroutine)
方法來處理計算任務,它可以調度協程的執行。任務對協程對象在事件循環的執行負責。如果被包裹的協程要從 future
(就是協程的實例化對象)調度,那么任務會被掛起,等待 future
的計算結果。
當 future
計算完成,被包裹的協程將會拿到 future 返回的結果或異常繼續執行。另外,需要注意的是,事件循環一次只能運行一個任務,除非還有其它事件循環在不同的線程并行運行,此任務才有可能和其他任務并行。當一個任務在等待 future
執行的期間,事件循環會運行一個新的任務。
在下面的代碼中,我們展示了三個可以被 Asyncio.Task()
并發執行的數學函數,在這個例子中,我們定義了三個協程, factorial
, fibonacci
和 binomialCoeff
,為了能并行執行這三個任務,我們將其放到一個 task
的 list
中得到事件循環然后運行任務,最后,關閉事件循環。
import asyncioasync def factorial(number):f = 1for i in range(2, number + 1):print("Asyncio.Task: Compute factorial(%s)" % (i))await asyncio.sleep(1)f *= iprint("Asyncio.Task - factorial(%s) = %s" % (number, f))async def fibonacci(number):a, b = 0, 1for i in range(number):print("Asyncio.Task: Compute fibonacci (%s)" % (i))await asyncio.sleep(1)a, b = b, a + bprint("Asyncio.Task - fibonacci(%s) = %s" % (number, a))async def binomialCoeff(n, k):result = 1for i in range(1, k+1):result = result * (n-i+1) / iprint("Asyncio.Task: Compute binomialCoeff (%s)" % (i))await asyncio.sleep(1)print("Asyncio.Task - binomialCoeff(%s , %s) = %s" % (n, k, result))if __name__ == "__main__":tasks = [asyncio.Task(factorial(10)),asyncio.Task(fibonacci(10)),asyncio.Task(binomialCoeff(20, 10))]loop = asyncio.get_event_loop()loop.run_until_complete(asyncio.wait(tasks))loop.close()
運行結果如下:
3.使用 Asyncio 和 Futures
future
是一個 Python
對象,它包含一個你希望在未來某個時間點獲得、但目前還不存在的值。通常,當創建 future
時,它沒有任何值,因為它還不存在。在這種狀態下,它被認為是不完整的、未解決的或根本沒有完成的。然后一旦你得到一個結果,就可以設置 future
的值,這將完成 future
。那時,我們可以認為它已經完成,并可從 future
中提取結果。
要操作 Asyncio
中的 Future
,必須進行以下聲明:
import asyncio
future = asyncio.Future()
基本的方法有:
方法 | 作用 |
---|---|
cancel() | 取消 future 的執行,調度回調函數 |
result() | 返回 future 代表的結果 |
exception() | 返回 future 中的 Exception |
add_done_callback(fn) | 添加一個回調函數,當 future 執行的時候會調用這個回調函數 |
remove_done_callback(fn) | 從“call whten done”列表中移除所有 callback 的實例 |
set_result(result) | 將 future 標為執行完成,并且設置 result 的值 |
set_exception(exception) | 將 future 標為執行完成,并設置 Exception |
import asyncio_# asyncio 里面有一個類 Future,實例化之后即可得到 future 對象_
_# 然后 asyncio 里面還有一個類 Task,實例化之后即可得到 task 對象(也就是任務)_
_# 這個 Task 是 Future 的子類,所以我們用的基本都是 task 對象,而不是 future 對象_
_# 但 Future 這個類和 asyncio 的實現有著密不可分的關系,所以我們必須單獨拿出來說_future = asyncio.Future()
print(future) _# <Future pending>_
print(future.__class__) _# <class '_asyncio.Future'>_
print(f"future 是否完成: {future.done()}") _# future 是否完成: False__# 設置一個值,通過 set_result_
future.set_result("古明地覺")
print(f"future 是否完成: {future.done()}") _# future 是否完成: True_
print(future) _# <Future finished result='古明地覺'>_
print(f"future 的返回值: {future.result()}") _# future 的返回值: 古明地覺_
可通過調用其類型對象 Future
來創建 future
,此時 future
上將沒有結果集,因此調用其 done
方法將返回 False
。此后用 set_result
方法設置 future
的值,這將把 future
標記為已完成。或者,如果想在 future
中設置一個異常,可調用 set_exception
。(必須在調用set_result(設置結果)之后才能調用result(獲取結果),并且set_result只能調用一次,但result可以調用多次)
在下面的示例代碼中,我們定義了一個函數 make_request
,該函數里面創建了一個 future
和一個任務,該任務將在 1 秒后異步設置 future
的結果。然后在主函數中調用 make_request
,當調用它時,將立即得到一個沒有結果的 future
。然后 await future
會讓主協程陷入等待,并將執行權交出去。一旦當 future
有值了,那么再恢復 main()
協程,拿到返回值進行處理。
import asyncioasync def set_future_value(future):await asyncio.sleep(1)future.set_result("Hello World")
def make_request():future = asyncio.Future()_# 創建一個任務來異步設置 future 的值_asyncio.create_task(set_future_value(future))return future
async def main():_# 注意這里的 make_request,它是一個普通的函數,如果在外部直接調用肯定是會報錯的__# 因為沒有事件循環,在執行 set_future_value 時會報錯__# 但如果在協程里面調用是沒問題的,因為協程運行時,事件循環已經啟動了__# 此時在 make_request 里面,會啟動一個任務_future = make_request()print(f"future 是否完成: {future.done()}")_# 阻塞等待,直到 future 有值,什么時候有值呢?__# 顯然是當協程 set_future_value 里面執行完 future.set_result 的時候_value = await future _# 暫停 main(),直到 future 的值被設置完成_print(f"future 是否完成: {future.done()}")print(value)
asyncio.run(main())