sta*_*ach 6 python python-asyncio
在阅读时:https : //docs.python.org/3/library/asyncio-task.html#asyncio.Task.cancel 似乎捕获 CancelledError 有两个目的。
一是可能会阻止您的任务被取消。
另一个是确定某些事情取消了您正在等待的任务。如何分辨?
async def cancel_me():
try:
await asyncio.sleep(3600)
except asyncio.CancelledError:
raise
finally:
print('cancel_me(): after sleep')
async def main():
task = asyncio.create_task(cancel_me())
await asyncio.sleep(1)
task.cancel()
try:
await task
except asyncio.CancelledError:
# HERE: How do I know if `task` has been cancelled, or I AM being cancelled?
print("main(): cancel_me is cancelled now")
Run Code Online (Sandbox Code Playgroud)
如何区分[我们自己被取消和我们正在等待的任务被取消]?
Asyncio 并不容易区分。当外部任务等待内部任务时,它将控制委托给内部协程。因此,取消任一任务都会将 aCancelledError注入完全相同的位置:await内部任务的最内部。这就是为什么您无法分辨最初取消了两个任务中的哪一个。
但是,可以通过打破awaits链并使用完成回调连接任务来规避该问题。然后在回调中拦截并检测内部任务的取消:
class ChildCancelled(asyncio.CancelledError):
pass
async def detect_cancel(task):
cont = asyncio.get_event_loop().create_future()
def on_done(_):
if task.cancelled():
cont.set_exception(ChildCancelled())
elif task.exception() is not None:
cont.set_exception(task.exception())
else:
cont.set_result(task.result())
task.add_done_callback(on_done)
await cont
Run Code Online (Sandbox Code Playgroud)
这在功能上等同于await task,除了它不task直接等待内部;它等待一个虚拟的未来,其结果在task完成后设置。在这一点上,我们可以用CancelledError更具体的ChildCancelled. 另一方面,如果外部任务被取消,它将显示为常规CancelledError的await cont,并将照常传播。
下面是一些测试代码:
import asyncio, sys
# async def detect_cancel defined as above
async def cancel_me():
print('cancel_me')
try:
await asyncio.sleep(3600)
finally:
print('cancel_me(): after sleep')
async def parent(task):
await asyncio.sleep(.001)
try:
await detect_cancel(task)
except ChildCancelled:
print("parent(): child is cancelled now")
raise
except asyncio.CancelledError:
print("parent(): I am cancelled")
raise
async def main():
loop = asyncio.get_event_loop()
child_task = loop.create_task(cancel_me())
parent_task = loop.create_task(parent(child_task))
await asyncio.sleep(.1) # give a chance to child to start running
if sys.argv[1] == 'parent':
parent_task.cancel()
else:
child_task.cancel()
await asyncio.sleep(.5)
asyncio.get_event_loop().run_until_complete(main())
Run Code Online (Sandbox Code Playgroud)
请注意,使用此实现,取消外部任务不会自动取消内部任务,但可以通过显式调用 轻松更改child.cancel(),无论是在 中parent,还是在detect_cancel本身。
Asyncio 使用类似的方法来实现 asyncio.shield().
| 归档时间: |
|
| 查看次数: |
422 次 |
| 最近记录: |