协程asyncio_Python 异步模块 asyncio 中的协程与任务
協程(Coroutine)是允許執行被掛起、恢復、以及取消的程序。Python 3 中最初是使用 @asyncio.coroutine 裝飾器和 yield from 關鍵字組合來實現協程。單詞 yield 在這里并非在生成器(Generator)中所表示的“產出”,而是交通標志中所表達的“讓步”之意。其實在生成器中也包含“讓步”的意思,即把執行權交給調用者,生成器暫緩執行,等待調用者對生成的結果處理完成,再恢復生成器的執行。在異步程序中,yield 則是把當前的執行權交給事件循環中的其它協程。Python 3.5 開始 async/await 被引入,Python 3.7 開始成為保留關鍵字,讓協程的使用更加方便和直觀。本文使用 Python 3.8。
使用 async def 定義一個協程:
async def main():print('hello coroutine')協程具體可以分為使用 async def 定義的協程函數和調用協程函數返回的協程對象。調用一個協程函數并不會執行協程中的程序,而是只返回一個協程對象。
asyncio 提供了三種方式來執行協程:
1. 使用 asyncio.run()
run() 函數接收一個協程對象,在執行時,總會創建一個新的事件循環,并在結束后關閉循環。理想情況下,run() 函數應當被作為程序的總入口,并且只會被調用一次。如果同一線程中還有其它事件循環在運行,則此方法不能被調用。
async def main():print('hello coroutine')asyncio.run(main())2. 使用 await 等待一個協程
await 的作用和 yield from 相同,即讓出當前的執行權,等待的對象有結果返回時,再重新獲得可以被繼續執行的權利。
只有可等待對象(Awaitable object)才能被 await。除協程(Coroutine)外,asyncio 還提供了兩種可等待對象:任務(Task)和期貨(Future)。
async def main():print('hello')await asyncio.sleep(1)await asyncio.sleep(2)print('coroutine')上述程序在打印出 hello 后會等待 3 秒再打印出 coroutine。存在多個 await 時,會依次順序執行,因為有兩條 sleep() 語句,分別等待 1 秒和 2 秒,所以一共用時 3 秒。
3. 使用 asyncio.create_task() 創建 Task
create_task() 會把一個協程打包成一個任務(Task),并立即排入日程準備執行,函數返回值是打包完成的 Task 對象。
async def foo(n):await asyncio.sleep(n)async def main():task1 = asyncio.create_task(foo(1))task2 = asyncio.create_task(foo(2))print('hello')await task1await task2print('coroutine')當使用 create_task() 時,創建的任務立即被加入到事件循環中,并不會阻塞當前的程序,所以上述程序在打印出 hello 后只需等待 2 秒就打印出 coroutine。
如上面所介紹,使用 create_task() 可以并發執行程序。asyncio 同時提供了幾個函數用于方便地實現多任務并發執行:
1. asyncio.gather(*aws, return_exceptions=False)
gather() 函數接受傳入多個可等待對象 aws,如果某個可等待對象是協程,則會被自動打包成 Task。gather() 返回結果是和 aws 傳入順序一致的列表。gather() 同時可以傳入參數 return_exceptions 來處理異常,默認值為 False。如果為 False 時,執行過程中引發的首個異常會立即返回給等待 gather() 的任務,await 會直接結束等待并拋出異常,但是其它正常執行的 Task 不會被取消,這種情況適用于確保任務盡可能被執行完成,但是不關心返回結果,因為如果有任何一個任務出現異常,返回結果列表就不會順利生成。如果把 return_exceptions 設為 True,異常會和正常結果一同被聚合進最終結果列表,適用于對結果有需求應用場景。
async 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=Trueresults = await asyncio.gather(task1, task2, return_exceptions=True)# 輸出: ['foo', RuntimeError('fake runtime error')]print(results)# 返回結果的順序和傳參順序一致assert isinstance(results[1], RuntimeError)# return_exceptions=Falsetry:results = await asyncio.gather(task1, task2, return_exceptions=False)# 此處打印并不會被執行, results 也未被賦值print(results)except RuntimeError as runtime_err:# 捕獲異常并打印: fake runtime errorprint(runtime_err)2. asyncio.wait(aws, *, timeout=None, return_when=ALL_COMPLETED)
wait() 接受 aws 任務集合傳入, 然后并發執行。參數 return_when 用來控制返回條件,當 return_when=ALL_COMPLETED 時,會在所有任務完成后返回結果;當 return_when=FIRST_COMPLETED 時,任務集合中有一個任務完成就立即返回結果。timeout 參數用來控制超時時間,可以是整數或浮點數,以秒為單位。wait() 的返回值是 (done, pending) 元組,done 中包含運行完成的任務,pending 中包含未完成被掛起的任務。
async def foo():await asyncio.sleep(3)return 'foo'async def bar():await asyncio.sleep(1)return 'bar'async def main():# 有一個任務執行完成即返回, 總共耗時 1 秒done, pending = await asyncio.wait({foo(), bar()}, return_when=asyncio.FIRST_COMPLETED)# done 集合里包含打包成 Task 的 bar()print(f'done: {done}')# pendding 集合里包含打包成 Task 的 foo()print(f'pending: {pending}')# 所有任務執行完成后返回, 總共耗時 3 秒done, pending = await asyncio.wait({foo(), bar()}, return_when=asyncio.ALL_COMPLETED)# done 集合里包含被帶打包成 Task 的 foo() 和 bar()print(f'done: {done}')# pending 集合為空print(f'pending: {pending}')# 所有任務執行完成, 但運行時間不能超 2 秒后返回, 總共耗時 2 秒done, pending = await asyncio.wait({foo(), bar()}, timeout=2, return_when=asyncio.ALL_COMPLETED)# done 集合里包含打包成 Task 的 bar()print(f'done: {done}')# pendding 集合里包含打包成 Task 的 foo()print(f'pending: {pending}')3. asyncio.as_completed(aws)
as_completed() 接受 aws 集合,然后返回一個 Future 迭代器,遍歷這個迭代器會依次遍歷剩余可等待對象集合中最早完成的結果。
async 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.wait_for(aw, timeout) 函數,該函數接受一個任務 aw 和超時時間 timeout,如果在限制時間內完成,則會正常返回,否則會被取消并拋出 asyncio.TimeoutError 異常。
為了防止任務被取消,可以使用 asyncio.shield(aw) 進行保護。shield() 會屏蔽外部取消操作,如果外部任務被取消,其內部正在執行的任務不會被取消,在內部看來取消操作并沒有發生,由于內部仍正常執行,執行完畢后會觸發 asyncio.CancelledError 異常,如果確保程序能忽略異常繼續執行,需要在外部使用 try-except 捕獲異常。如果在任務內部取消,則會被成功取消。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的协程asyncio_Python 异步模块 asyncio 中的协程与任务的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 最简单的调漂方法教程(新手必学钓浮调漂的
- 下一篇: 红米note7用USB怎么连接电脑传文件