Sor*_*ary 12 python asynchronous contextmanager python-3.x python-asyncio
这是我的代码的简化版本:
main
是一个在第二次迭代后停止的协程。
get_numbers
是一个异步生成器,它生成数字,但位于异步上下文管理器中。
import asyncio
class MyContextManager:
async def __aenter__(self):
print("Enter to the Context Manager...")
return self
async def __aexit__(self, exc_type, exc_value, exc_tb):
print(exc_type)
print("Exit from the Context Manager...")
await asyncio.sleep(1)
print("This line is not executed") # <-------------------
await asyncio.sleep(1)
async def get_numbers():
async with MyContextManager():
for i in range(30):
yield i
async def main():
async for i in get_numbers():
print(i)
if i == 1:
break
asyncio.run(main())
Run Code Online (Sandbox Code Playgroud)
输出是:
Enter to the Context Manager...
0
1
<class 'asyncio.exceptions.CancelledError'>
Exit from the Context Manager...
Run Code Online (Sandbox Code Playgroud)
我其实有两个问题:
__aexit__
执行的机会。但该行print("This line is not executed")
没有被执行。这是为什么?假设如果我们await
在 中有一条语句__aexit__
,那么该行之后的代码根本不会执行,并且我们不应该依赖它来进行清理,这样的假设是否正确?help()
表明: | aclose(...)
| aclose() -> raise GeneratorExit inside generator.
Run Code Online (Sandbox Code Playgroud)
那么为什么我<class 'asyncio.exceptions.CancelledError'>
在 里面遇到异常__aexit__
?
* 我使用的是Python 3.10.4
这不是特定于__aexit__
而是所有异步代码:当事件循环关闭时,它必须决定是取消剩余任务还是保留它们。为了清理的目的,大多数框架更喜欢取消,而不是依赖程序员稍后清理保留的任务。
这种关闭清理是一种独立的机制,与正常执行期间调用堆栈上的函数、上下文和类似内容的正常展开不同。还必须在取消期间进行清理的上下文管理器必须为此做好专门准备。不过,在许多情况下,不为此做好准备也没有什么问题,因为许多资源会自行失效。
\n在当代的事件循环框架中,通常存在三个级别的清理:
\n__aexit__
当范围结束时调用,并且可能会收到触发展开作为参数的异常。预计清理工作将根据需要推迟。这与运行同步代码相当__exit__
。__aexit__
可以接收CancelledError
1作为参数或作为任何//的异常await
async for
async with
。清理工作可能会延迟,但预计会尽快进行。这与取消同步代码相当KeyboardInterrupt
。__aexit__
可以接收 aGeneratorExit
作为参数或作为任何//的异常await
async for
async with
。清理工作必须尽快进行。这相当于GeneratorExit
关闭同步发电机。要处理取消/关闭,任何async
代码 \xe2\x80\x93 无论是在__aexit__
\xe2\x80\x93 中还是其他地方,都必须期望处理CancelledError
or GeneratorExit
。前者可能会被延迟或抑制,但后者应该立即、同步处理2。
async def __aexit__(self, exc_type, exc_value, exc_tb):\n print("Exit from the Context Manager...")\n try:\n await asyncio.sleep(1) # an exception may arrive here\n except GeneratorExit:\n print("Exit stage left NOW")\n raise\n except asyncio.CancelledError:\n print("Got cancelled, just cleaning up a few things...")\n await asyncio.sleep(0.5)\n raise\n else:\n print("Nothing to see here, taking my time on the way out")\n await asyncio.sleep(1)\n
Run Code Online (Sandbox Code Playgroud)\n注意:通常不可能详尽地处理这些情况。不同形式的清理可能会相互中断,例如取消展开然后关闭。只有尽最大努力才能进行清理;强大的清理是通过故障安全来实现的,例如通过事务,而不是显式清理。
\n具体来说,异步生成器的清理是一个棘手的情况,因为它们可以在所有情况下同时清理:在生成器完成时展开,在拥有任务被销毁时取消,或者在生成器被垃圾收集时关闭。清理信号到达的顺序取决于实现。
\n解决这个问题的正确方法是首先不要依赖隐式清理。相反,每个协程应确保其所有子资源在父级退出之前关闭。值得注意的是,异步生成器可能会占用资源并需要关闭。
\n async def __aexit__(self, exc_type, exc_value, exc_tb):\n print("Exit from the Context Manager...")\n try:\n await asyncio.sleep(1) # an exception may arrive here\n except GeneratorExit:\n print("Exit stage left NOW")\n raise\n except asyncio.CancelledError:\n print("Got cancelled, just cleaning up a few things...")\n await asyncio.sleep(0.5)\n raise\n else:\n print("Nothing to see here, taking my time on the way out")\n await asyncio.sleep(1)\n
Run Code Online (Sandbox Code Playgroud)\naclosing
在最新版本中,此模式通过上下文管理器进行编码。
async def main():\n # create a generator that might need cleanup\n async_iter = get_numbers()\n async for i in async_iter:\n print(i)\n if i == 1:\n break\n # wait for generator clean up before exiting\n await async_iter.aclose()\n
Run Code Online (Sandbox Code Playgroud)\n1此例外的名称和/或身份可能有所不同。
\n2虽然可以await
在 期间进行异步处理GeneratorExit
,但它们可能不会屈服于事件循环。同步接口有利于强制执行此操作。
归档时间: |
|
查看次数: |
3148 次 |
最近记录: |