協(xié)程與任務(wù)?
本節(jié)將簡述用于協(xié)程與任務(wù)的高層級(jí) API。
協(xié)程?
協(xié)程通過 async/await 語法進(jìn)行聲明,是編寫異步應(yīng)用的推薦方式。例如,以下代碼段 (需要 Python 3.7+) 打印 "hello",等待 1 秒,然后打印 "world":
>>> import asyncio
>>> async def main():
... print('hello')
... await asyncio.sleep(1)
... print('world')
>>> asyncio.run(main())
hello
world
注意:簡單地調(diào)用一個(gè)協(xié)程并不會(huì)將其加入執(zhí)行日程:
>>> main()
<coroutine object main at 0x1053bb7c8>
要真正運(yùn)行一個(gè)協(xié)程,asyncio 提供了三種主要機(jī)制:
asyncio.run()函數(shù)用來運(yùn)行最高層級(jí)的入口點(diǎn) "main()" 函數(shù) (參見上面的示例。)等待一個(gè)協(xié)程。以下代碼段會(huì)在等待 1 秒后打印 "hello",然后 再次 等待 2 秒后打印 "world":
import asyncio import time async def say_after(delay, what): await asyncio.sleep(delay) print(what) async def main(): print(f"started at {time.strftime('%X')}") await say_after(1, 'hello') await say_after(2, 'world') print(f"finished at {time.strftime('%X')}") asyncio.run(main())
預(yù)期的輸出:
started at 17:13:52 hello world finished at 17:13:55
asyncio.create_task()函數(shù)用來并發(fā)運(yùn)行作為 asyncio任務(wù)的多個(gè)協(xié)程。讓我們修改以上示例,并發(fā) 運(yùn)行兩個(gè)
say_after協(xié)程:async def main(): task1 = asyncio.create_task( say_after(1, 'hello')) task2 = asyncio.create_task( say_after(2, 'world')) print(f"started at {time.strftime('%X')}") # Wait until both tasks are completed (should take # around 2 seconds.) await task1 await task2 print(f"finished at {time.strftime('%X')}")
注意,預(yù)期的輸出顯示代碼段的運(yùn)行時(shí)間比之前快了 1 秒:
started at 17:14:32 hello world finished at 17:14:34
可等待對象?
如果一個(gè)對象可以在 await 語句中使用,那么它就是 可等待 對象。許多 asyncio API 都被設(shè)計(jì)為接受可等待對象。
可等待 對象有三種主要類型: 協(xié)程, 任務(wù) 和 Future.
協(xié)程
Python 協(xié)程屬于 可等待 對象,因此可以在其他協(xié)程中被等待:
import asyncio
async def nested():
return 42
async def main():
# Nothing happens if we just call "nested()".
# A coroutine object is created but not awaited,
# so it *won't run at all*.
nested()
# Let's do it differently now and await it:
print(await nested()) # will print "42".
asyncio.run(main())
重要
在本文檔中 "協(xié)程" 可用來表示兩個(gè)緊密關(guān)聯(lián)的概念:
協(xié)程函數(shù): 定義形式為
async def的函數(shù);協(xié)程對象: 調(diào)用 協(xié)程函數(shù) 所返回的對象。
asyncio 也支持舊式的 基于生成器的 協(xié)程。
任務(wù)
任務(wù) 被用來設(shè)置日程以便 并發(fā) 執(zhí)行協(xié)程。
當(dāng)一個(gè)協(xié)程通過 asyncio.create_task() 等函數(shù)被打包為一個(gè) 任務(wù),該協(xié)程將自動(dòng)排入日程準(zhǔn)備立即運(yùn)行:
import asyncio
async def nested():
return 42
async def main():
# Schedule nested() to run soon concurrently
# with "main()".
task = asyncio.create_task(nested())
# "task" can now be used to cancel "nested()", or
# can simply be awaited to wait until it is complete:
await task
asyncio.run(main())
Futures
Future 是一種特殊的 低層級(jí) 可等待對象,表示一個(gè)異步操作的 最終結(jié)果。
當(dāng)一個(gè) Future 對象 被等待,這意味著協(xié)程將保持等待直到該 Future 對象在其他地方操作完畢。
在 asyncio 中需要 Future 對象以便允許通過 async/await 使用基于回調(diào)的代碼。
通常情況下 沒有必要 在應(yīng)用層級(jí)的代碼中創(chuàng)建 Future 對象。
Future 對象有時(shí)會(huì)由庫和某些 asyncio API 暴露給用戶,用作可等待對象:
async def main():
await function_that_returns_a_future_object()
# this is also valid:
await asyncio.gather(
function_that_returns_a_future_object(),
some_python_coroutine()
)
一個(gè)很好的返回對象的低層級(jí)函數(shù)的示例是 loop.run_in_executor()。
運(yùn)行 asyncio 程序?
-
asyncio.run(coro, *, debug=False)? 執(zhí)行 coroutine coro 并返回結(jié)果。
此函數(shù)運(yùn)行傳入的協(xié)程,負(fù)責(zé)管理 asyncio 事件循環(huán)并 完結(jié)異步生成器。
當(dāng)有其他 asyncio 事件循環(huán)在同一線程中運(yùn)行時(shí),此函數(shù)不能被調(diào)用。
如果 debug 為
True,事件循環(huán)將以調(diào)試模式運(yùn)行。此函數(shù)總是會(huì)創(chuàng)建一個(gè)新的事件循環(huán)并在結(jié)束時(shí)關(guān)閉之。它應(yīng)當(dāng)被用作 asyncio 程序的主入口點(diǎn),理想情況下應(yīng)當(dāng)只被調(diào)用一次。
示例:
async def main(): await asyncio.sleep(1) print('hello') asyncio.run(main())
3.7 新版功能: 重要: 此函數(shù)是在 Python 3.7 中加入 asyncio 模塊,處于 暫定基準(zhǔn)狀態(tài)。
創(chuàng)建任務(wù)?
-
asyncio.create_task(coro)? 將 coro 協(xié)程 打包為一個(gè)
Task排入日程準(zhǔn)備執(zhí)行。返回 Task 對象。該任務(wù)會(huì)在
get_running_loop()返回的循環(huán)中執(zhí)行,如果當(dāng)前線程沒有在運(yùn)行的循環(huán)則會(huì)引發(fā)RuntimeError。此函數(shù) 在 Python 3.7 中被加入。在 Python 3.7 之前,可以改用低層級(jí)的
asyncio.ensure_future()函數(shù)。async def coro(): ... # In Python 3.7+ task = asyncio.create_task(coro()) ... # This works in all Python versions but is less readable task = asyncio.ensure_future(coro()) ...
3.7 新版功能.
休眠?
-
coroutine
asyncio.sleep(delay, result=None, *, loop=None)? 阻塞 delay 指定的秒數(shù)。
如果指定了 result,則當(dāng)協(xié)程完成時(shí)將其返回給調(diào)用者。
sleep()總是會(huì)掛起當(dāng)前任務(wù),以允許其他任務(wù)運(yùn)行。loop 參數(shù)已棄用,計(jì)劃在 Python 3.10 中移除。
以下協(xié)程示例運(yùn)行 5 秒,每秒顯示一次當(dāng)前日期:
import asyncio import datetime async def display_date(): loop = asyncio.get_running_loop() end_time = loop.time() + 5.0 while True: print(datetime.datetime.now()) if (loop.time() + 1.0) >= end_time: break await asyncio.sleep(1) asyncio.run(display_date())
并發(fā)運(yùn)行任務(wù)?
-
awaitable
asyncio.gather(*aws, loop=None, return_exceptions=False)? 并發(fā) 運(yùn)行 aws 序列中的 可等待對象。
如果 aws 中的某個(gè)可等待對象為協(xié)程,它將自動(dòng)作為一個(gè)任務(wù)加入日程。
如果所有可等待對象都成功完成,結(jié)果將是一個(gè)由所有返回值聚合而成的列表。結(jié)果值的順序與 aws 中可等待對象的順序一致。
如果 return_exceptions 為
False(默認(rèn)),所引發(fā)的首個(gè)異常會(huì)立即傳播給等待gather()的任務(wù)。aws 序列中的其他可等待對象 不會(huì)被取消 并將繼續(xù)運(yùn)行。如果 return_exceptions 為
True,異常會(huì)和成功的結(jié)果一樣處理,并聚合至結(jié)果列表。如果
gather()被取消,所有被提交 (尚未完成) 的可等待對象也會(huì) 被取消。如果 aws 序列中的任一 Task 或 Future 對象 被取消,它將被當(dāng)作引發(fā)了
CancelledError一樣處理 -- 在此情況下gather()調(diào)用 不會(huì) 被取消。這是為了防止一個(gè)已提交的 Task/Future 被取消導(dǎo)致其他 Tasks/Future 也被取消。示例:
import asyncio async def factorial(name, number): f = 1 for i in range(2, number + 1): print(f"Task {name}: Compute factorial({i})...") await asyncio.sleep(1) f *= i print(f"Task {name}: factorial({number}) = {f}") async def main(): # Schedule three calls *concurrently*: await asyncio.gather( factorial("A", 2), factorial("B", 3), factorial("C", 4), ) asyncio.run(main()) # Expected output: # # Task A: Compute factorial(2)... # Task B: Compute factorial(2)... # Task C: Compute factorial(2)... # Task A: factorial(2) = 2 # Task B: Compute factorial(3)... # Task C: Compute factorial(3)... # Task B: factorial(3) = 6 # Task C: Compute factorial(4)... # Task C: factorial(4) = 24
在 3.7 版更改: 如果 gather 本身被取消,則無論 return_exceptions 取值為何,消息都會(huì)被傳播。
屏蔽取消操作?
-
awaitable
asyncio.shield(aw, *, loop=None)? -
如果 aw 是一個(gè)協(xié)程,它將自動(dòng)作為任務(wù)加入日程。
以下語句:
res = await shield(something())
相當(dāng)于:
res = await something()
不同之處 在于如果包含它的協(xié)程被取消,在
something()中運(yùn)行的任務(wù)不會(huì)被取消。從something()的角度看來,取消操作并沒有發(fā)生。然而其調(diào)用者已被取消,因此 "await" 表達(dá)式仍然會(huì)引發(fā)CancelledError。如果通過其他方式取消
something()(例如在其內(nèi)部操作) 則shield()也會(huì)取消。如果希望完全忽略取消操作 (不推薦) 則
shield()函數(shù)需要配合一個(gè) try/except 代碼段,如下所示:try: res = await shield(something()) except CancelledError: res = None
超時(shí)?
-
coroutine
asyncio.wait_for(aw, timeout, *, loop=None)? 等待 aw 可等待對象 完成,指定 timeout 秒數(shù)后超時(shí)。
如果 aw 是一個(gè)協(xié)程,它將自動(dòng)作為任務(wù)加入日程。
timeout 可以為
None,也可以為 float 或 int 型數(shù)值表示的等待秒數(shù)。如果 timeout 為None,則等待直到完成。如果發(fā)生超時(shí),任務(wù)將取消并引發(fā)
asyncio.TimeoutError.函數(shù)將等待直到目標(biāo)對象確實(shí)被取消,所以總等待時(shí)間可能超過 timeout 指定的秒數(shù)。
如果等待被取消,則 aw 指定的對象也會(huì)被取消。
loop 參數(shù)已棄用,計(jì)劃在 Python 3.10 中移除。
示例:
async def eternity(): # Sleep for one hour await asyncio.sleep(3600) print('yay!') async def main(): # Wait for at most 1 second try: await asyncio.wait_for(eternity(), timeout=1.0) except asyncio.TimeoutError: print('timeout!') asyncio.run(main()) # Expected output: # # timeout!
在 3.7 版更改: 當(dāng) aw 因超時(shí)被取消,
wait_for會(huì)等待 aw 被取消。之前版本則將立即引發(fā)asyncio.TimeoutError。
簡單等待?
-
coroutine
asyncio.wait(aws, *, loop=None, timeout=None, return_when=ALL_COMPLETED)? 并發(fā)運(yùn)行 aws 指定的 可等待對象 并阻塞線程直到滿足 return_when 指定的條件。
如果 aws 中的某個(gè)可等待對象為協(xié)程,它將自動(dòng)作為任務(wù)加入日程。直接向
wait()傳入?yún)f(xié)程對象已棄用,因?yàn)檫@會(huì)導(dǎo)致 令人迷惑的行為。返回兩個(gè) Task/Future 集合:
(done, pending)。用法:
done, pending = await asyncio.wait(aws)
loop 參數(shù)已棄用,計(jì)劃在 Python 3.10 中移除。
如指定 timeout (float 或 int 類型) 則它將被用于控制返回之前等待的最長秒數(shù)。
請注意此函數(shù)不會(huì)引發(fā)
asyncio.TimeoutError。當(dāng)超時(shí)發(fā)生時(shí),未完成的 Future 或 Task 將在指定秒數(shù)后被返回。return_when 指定此函數(shù)應(yīng)在何時(shí)返回。它必須為以下常數(shù)之一:
常數(shù)
描述
FIRST_COMPLETED函數(shù)將在任意可等待對象結(jié)束或取消時(shí)返回。
FIRST_EXCEPTION函數(shù)將在任意可等待對象因引發(fā)異常而結(jié)束時(shí)返回。當(dāng)沒有引發(fā)任何異常時(shí)它就相當(dāng)于
ALL_COMPLETED。ALL_COMPLETED函數(shù)將在所有可等待對象結(jié)束或取消時(shí)返回。
與
wait_for()不同,wait()在超時(shí)發(fā)生時(shí)不會(huì)取消可等待對象。注解
wait()會(huì)自動(dòng)將協(xié)程作為任務(wù)加入日程,以后將以(done, pending)集合形式返回顯式創(chuàng)建的任務(wù)對象。因此以下代碼并不會(huì)有預(yù)期的行為:async def foo(): return 42 coro = foo() done, pending = await asyncio.wait({coro}) if coro in done: # This branch will never be run!
以上代碼段的修正方法如下:
async def foo(): return 42 task = asyncio.create_task(foo()) done, pending = await asyncio.wait({task}) if task in done: # Everything will work as expected now.
直接向
wait()傳入?yún)f(xié)程對象的方式已棄用。
-
asyncio.as_completed(aws, *, loop=None, timeout=None)? ?并發(fā)地運(yùn)行 aws 集合中的 可等待對象。返回一個(gè)
Future對象的迭代器。返回的每個(gè) Future 對象代表來自剩余可等待對象集合的最早結(jié)果。如果在所有 Future 對象完成前發(fā)生超時(shí)則將引發(fā)
asyncio.TimeoutError。示例:
for f in as_completed(aws): earliest_result = await f # ...
來自其他線程的日程安排?
-
asyncio.run_coroutine_threadsafe(coro, loop)? 向指定事件循環(huán)提交一個(gè)協(xié)程。線程安全。
返回一個(gè)
concurrent.futures.Future以等待來自其他 OS 線程的結(jié)果。此函數(shù)應(yīng)該從另一個(gè) OS 線程中調(diào)用,而非事件循環(huán)運(yùn)行所在線程。示例:
# Create a coroutine coro = asyncio.sleep(1, result=3) # Submit the coroutine to a given loop future = asyncio.run_coroutine_threadsafe(coro, loop) # Wait for the result with an optional timeout argument assert future.result(timeout) == 3
如果在協(xié)程內(nèi)產(chǎn)生了異常,將會(huì)通知返回的 Future 對象。它也可被用來取消事件循環(huán)中的任務(wù):
try: result = future.result(timeout) except asyncio.TimeoutError: print('The coroutine took too long, cancelling the task...') future.cancel() except Exception as exc: print(f'The coroutine raised an exception: {exc!r}') else: print(f'The coroutine returned: {result!r}')
參見 concurrency and multithreading 部分的文檔。
不同與其他 asyncio 函數(shù),此函數(shù)要求顯式地傳入 loop 參數(shù)。
3.5.1 新版功能.
內(nèi)省?
-
asyncio.current_task(loop=None)? 返回當(dāng)前運(yùn)行的
Task實(shí)例,如果沒有正在運(yùn)行的任務(wù)則返回None。如果 loop 為
None則會(huì)使用get_running_loop()獲取當(dāng)前事件循環(huán)。3.7 新版功能.
-
asyncio.all_tasks(loop=None)? 返回事件循環(huán)所運(yùn)行的未完成的
Task對象的集合。如果 loop 為
None,則會(huì)使用get_running_loop()獲取當(dāng)前事件循環(huán)。3.7 新版功能.
Task 對象?
-
class
asyncio.Task(coro, *, loop=None)? 一個(gè)與
Future 類似的對象,可運(yùn)行 Python 協(xié)程。非線程安全。Task 對象被用來在事件循環(huán)中運(yùn)行協(xié)程。如果一個(gè)協(xié)程在等待一個(gè) Future 對象,Task 對象會(huì)掛起該協(xié)程的執(zhí)行并等待該 Future 對象完成。當(dāng)該 Future 對象 完成,被打包的協(xié)程將恢復(fù)執(zhí)行。
事件循環(huán)使用協(xié)同日程調(diào)度: 一個(gè)事件循環(huán)每次運(yùn)行一個(gè) Task 對象。而一個(gè) Task 對象會(huì)等待一個(gè) Future 對象完成,該事件循環(huán)會(huì)運(yùn)行其他 Task、回調(diào)或執(zhí)行 IO 操作。
使用高層級(jí)的
asyncio.create_task()函數(shù)來創(chuàng)建 Task 對象,也可用低層級(jí)的loop.create_task()或ensure_future()函數(shù)。不建議手動(dòng)實(shí)例化 Task 對象。要取消一個(gè)正在運(yùn)行的 Task 對象可使用
cancel()方法。調(diào)用此方法將使該 Task 對象拋出一個(gè)CancelledError異常給打包的協(xié)程。如果取消期間一個(gè)協(xié)程正在等待一個(gè) Future 對象,該 Future 對象也將被取消。cancelled()可被用來檢測 Task 對象是否被取消。如果打包的協(xié)程沒有抑制CancelledError異常并且確實(shí)被取消,該方法將返回True。asyncio.Task從Future繼承了其除Future.set_result()和Future.set_exception()以外的所有 API。Task 對象支持
contextvars模塊。當(dāng)一個(gè) Task 對象被創(chuàng)建,它將復(fù)制當(dāng)前上下文,然后在復(fù)制的上下文中運(yùn)行其協(xié)程。在 3.7 版更改: 加入對
contextvars模塊的支持。-
cancel()? 請求取消 Task 對象。
這將安排在下一輪事件循環(huán)中拋出一個(gè)
CancelledError異常給被封包的協(xié)程。協(xié)程在之后有機(jī)會(huì)進(jìn)行清理甚至使用
try... ...except CancelledError...finally代碼塊抑制異常來拒絕請求。不同于Future.cancel(),Task.cancel()不保證 Task 會(huì)被取消,雖然抑制完全取消并不常見,也很不鼓勵(lì)這樣做。以下示例演示了協(xié)程是如何偵聽取消請求的:
async def cancel_me(): print('cancel_me(): before sleep') try: # Wait for 1 hour await asyncio.sleep(3600) except asyncio.CancelledError: print('cancel_me(): cancel sleep') raise finally: print('cancel_me(): after sleep') async def main(): # Create a "cancel_me" Task task = asyncio.create_task(cancel_me()) # Wait for 1 second await asyncio.sleep(1) task.cancel() try: await task except asyncio.CancelledError: print("main(): cancel_me is cancelled now") asyncio.run(main()) # Expected output: # # cancel_me(): before sleep # cancel_me(): cancel sleep # cancel_me(): after sleep # main(): cancel_me is cancelled now
-
cancelled()? 如果 Task 對象 被取消 則返回
True。當(dāng)使用
cancel()發(fā)出取消請求時(shí) Task 會(huì)被 取消,其封包的協(xié)程將傳播被拋入的CancelledError異常。
-
done()? 如果 Task 對象 已完成 則返回
True。當(dāng) Task 所封包的協(xié)程返回一個(gè)值、引發(fā)一個(gè)異?;?Task 本身被取消時(shí),則會(huì)被認(rèn)為 已完成。
-
result()? 返回 Task 的結(jié)果。
如果 Task 對象 已完成,其封包的協(xié)程的結(jié)果會(huì)被返回 (或者當(dāng)協(xié)程引發(fā)異常時(shí),該異常會(huì)被重新引發(fā)。)
如果 Task 對象 被取消,此方法會(huì)引發(fā)一個(gè)
CancelledError異常。如果 Task 對象的結(jié)果還不可用,此方法會(huì)引發(fā)一個(gè)
InvalidStateError異常。
-
exception()? 返回 Task 對象的異常。
如果所封包的協(xié)程引發(fā)了一個(gè)異常,該異常將被返回。如果所封包的協(xié)程正常返回則該方法將返回
None。如果 Task 對象 被取消,此方法會(huì)引發(fā)一個(gè)
CancelledError異常。如果 Task 對象尚未 完成,此方法將引發(fā)一個(gè)
InvalidStateError異常。
-
add_done_callback(callback, *, context=None)? 添加一個(gè)回調(diào),將在 Task 對象 完成 時(shí)被運(yùn)行。
此方法應(yīng)該僅在低層級(jí)的基于回調(diào)的代碼中使用。
要了解更多細(xì)節(jié)請查看
Future.add_done_callback()的文檔。
-
remove_done_callback(callback)? 從回調(diào)列表中移除 callback 。
此方法應(yīng)該僅在低層級(jí)的基于回調(diào)的代碼中使用。
要了解更多細(xì)節(jié)請查看
Future.remove_done_callback()的文檔。
-
get_stack(*, limit=None)? 返回此 Task 對象的??蚣芰斜怼?/p>
如果所封包的協(xié)程未完成,這將返回其掛起所在的棧。如果協(xié)程已成功完成或被取消,這將返回一個(gè)空列表。如果協(xié)程被一個(gè)異常終止,這將返回回溯框架列表。
框架總是從按從舊到新排序。
每個(gè)被掛起的協(xié)程只返回一個(gè)??蚣?。
可選的 limit 參數(shù)指定返回框架的數(shù)量上限;默認(rèn)返回所有框架。返回列表的順序要看是返回一個(gè)棧還是一個(gè)回溯:棧返回最新的框架,回溯返回最舊的框架。(這與 traceback 模塊的行為保持一致。)
-
print_stack(*, limit=None, file=None)? 打印此 Task 對象的棧或回溯。
此方法產(chǎn)生的輸出類似于 traceback 模塊通過
get_stack()所獲取的框架。limit 參數(shù)會(huì)直接傳遞給
get_stack()。file 參數(shù)是輸出所寫入的 I/O 流;默認(rèn)情況下輸出會(huì)寫入
sys.stderr。
-
classmethod
all_tasks(loop=None)? 返回一個(gè)事件循環(huán)中所有任務(wù)的集合。
默認(rèn)情況下將返回當(dāng)前事件循環(huán)中所有任務(wù)。如果 loop 為
None,則會(huì)使用get_event_loop()函數(shù)來獲取當(dāng)前事件循環(huán)。此方法 已棄用 并將在 Python 3.9 中移除。請改用
asyncio.all_tasks()函數(shù)。
-
classmethod
current_task(loop=None)? 返回當(dāng)前運(yùn)行任務(wù)或
None。如果 loop 為
None,則會(huì)使用get_event_loop()函數(shù)來獲取當(dāng)前事件循環(huán)。此方法 已棄用 并將在 Python 3.9 中移除。請改用
asyncio.current_task()函數(shù)。
-
基于生成器的協(xié)程?
注解
對基于生成器的協(xié)程的支持 已棄用 并計(jì)劃在 Python 3.10 中移除。
基于生成器的協(xié)程是 async/await 語法的前身。它們是使用 yield from 語句創(chuàng)建的 Python 生成器,可以等待 Future 和其他協(xié)程。
基于生成器的協(xié)程應(yīng)該使用 @asyncio.coroutine 裝飾,雖然這并非強(qiáng)制。
-
@asyncio.coroutine? 用來標(biāo)記基于生成器的協(xié)程的裝飾器。
此裝飾器使得舊式的基于生成器的協(xié)程能與 async/await 代碼相兼容:
@asyncio.coroutine def old_style_coroutine(): yield from asyncio.sleep(1) async def main(): await old_style_coroutine()
此裝飾器 已棄用 并計(jì)劃在 Python 3.10 中移除。
此裝飾器不應(yīng)該被用于
async def協(xié)程。
-
asyncio.iscoroutine(obj)? 如果 obj 是一個(gè) 協(xié)程對象 則返回
True。此方法不同于
inspect.iscoroutine()因?yàn)樗鼘谏善鞯膮f(xié)程返回True。
-
asyncio.iscoroutinefunction(func)? 如果 func 是一個(gè) 協(xié)程函數(shù) 則返回
True。此方法不同于
inspect.iscoroutinefunction()因?yàn)樗鼘σ?@coroutine裝飾的基于生成器的協(xié)程函數(shù)返回True。
