Python asyncio shield 函式說明
Posted on Aug 15, 2021 in Python 程式設計 - 高階 by Amo Chen ‐ 2 min read
前陣子研究 asyncio 模組時,恰好看到其中 1 個函式 wait_for() ,該函式在 coroutine 超過時限時,會將其取消(cancel),不過 asyncio 也有提供 1 個函式能夠保護 task 被取消,該函式為 shield() ,官方文件對於 shield()
函式的說明為:
Protect an awaitable object from being cancelled .
簡而言之,能夠防止 awaitables(coroutines, Tasks, Futures) 物件被取消。
不過,實際上該怎麼使用,官方文件並沒有著墨太多,因此本文將實際透過幾個範例摸索如何使用 shield() .
本文環境
- Python 3.9
asyncio.shield()
官方文件中提到,如果想要防止 task 被取消,可以用 shield()
函式包住該 task:
To avoid the task cancellation , wrap it in shield() .
例如以下範例:
import asyncio
async def do_async_job():
await asyncio.sleep(2)
print('protect me from cancelling!')
async def main():
task = asyncio.create_task(do_async_job())
shield = asyncio.shield(task)
print("shield's type =>", type(shield))
try:
await asyncio.wait_for(shield, timeout=1)
except asyncio.TimeoutError:
print('timeout!')
asyncio.run(main())
上述範例以 asyncio.shield(task)
包住 do_async_job()
這個 task, 理論上我們預期看到 asyncio.wait_for()
失去效果,希望因爲 asyncio.shield()
的效果,讓 asyncio.wait_for()
無法取消 task
的執行,最後導致 print('protect me from cancelling!')
仍被執行,而列印出 protect me from cancelling!
字串。
但是實際執行結果如下,只列印 timeout
之後就結束了:
$ python test_shield.py
shield's type => <class '_asyncio.Future'>
timeout!
這是為什麼?
先透過呼叫 canceled() 方法進一步查看 shield
與 task
的狀態,於第 17, 18 行查看 shield
與 task
2 個是否有被取消:
import asyncio
async def do_async_job():
await asyncio.sleep(2)
print('protect me from cancelling!')
async def main():
task = asyncio.create_task(do_async_job())
shield = asyncio.shield(task)
print("shield's type =>", type(shield))
try:
await asyncio.wait_for(shield, timeout=1)
except asyncio.TimeoutError:
print('timeout!')
print(f'shield canceled: {shield.cancelled()}')
print(f'task canceled: {task.cancelled()}')
asyncio.run(main())
上述執行結果如下,從結果可以發現 task
確實沒有被取消,只有 shield
被取消而已:
$ python test_shield.py
shield's type => <class '_asyncio.Future'>
timeout!
shield canceled: True
task canceled: False
上述行為正好呼應官方文件的說明:
From the point of view of something(), the cancellation did not happen. Although its caller is still cancelled, so the “await” expression still raises a CancelledError .
對於 task 而言,它並沒有被取消,但對於 asyncio.wait_for()
而言,它取消的其實是 asyncio.shield()
所回傳的 Future 物件,所以 asyncio.wait_for()
依然會 raise TimeoutError
, 因而造成列印完 timeout
就結束程式。
知道原因之後,我們就能夠修正錯誤,只要在 raise TimeoutError
後,以 await task
等待其執行即可:
import asyncio
async def do_async_job():
await asyncio.sleep(2)
print('protect me from cancelling!')
async def main():
task = asyncio.create_task(do_async_job())
shield = asyncio.shield(task)
try:
await asyncio.wait_for(shield, timeout=1)
except asyncio.TimeoutError:
await task
asyncio.run(main())
上述執行結果如下,得到我們預期的結果了!
$ python test_shield.py
protect me from cancelling!
以上就是使用 asyncio.shield() 可能需要注意的地方。
Happy coding!
References
https://docs.python.org/3/library/asyncio-task.html#shielding-from-cancellation