這是我的代碼的簡化版本:
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())
輸出是:
Enter to the Context Manager...
0
1
<class 'asyncio.exceptions.CancelledError'>
Exit from the Context Manager...
我實際上有兩個問題:
- 據我了解,AsyncIO 會安排一個 Task在事件回圈的下一個周期中很快被呼叫
__aexit__,并提供執行的機會。但是該行print("This line is not executed")沒有被執行。這是為什么?假設如果我們在await里面有一個陳述句__aexit__,那一行之后的代碼根本不會執行,我們不應該依賴它來清理,這是否正確?
- 異步生成器的輸出
help()顯示:
| aclose(...)
| aclose() -> raise GeneratorExit inside generator.
那么為什么我<class 'asyncio.exceptions.CancelledError'>在里面得到例外__aexit__?
* 我正在使用 Python 3.10.4
uj5u.com熱心網友回復:
我不確定發生了什么,但發布我發現的內容,以防它證明對決定調查的其他人有用。get_numbers()當我們存盤對外部的參考時,輸出變為預期main()。我會說這似乎get_numbers()是垃圾收集到早期,但禁用gc沒有幫助,所以我的猜測可能是錯誤的。
import asyncio
test = None
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") # <-- Executed now
await asyncio.sleep(1)
async def get_numbers():
async with MyContextManager():
for i in range(30):
yield i
async def main():
global test
test = get_numbers()
async for i in test:
print(i)
if i == 1:
break
asyncio.run(main())
uj5u.com熱心網友回復:
答案很簡單:解釋器會__aexit__在一秒鐘后繼續執行,但是main函式已經完成,并且沒有指向背景關系管理器的指標。
您自己提到的第一個明顯的解決方案是在 main 函式之后等待足夠長的時間:
async def main():
async for i in get_numbers():
print(i)
if i == 1:
break
await asyncio.sleep(4) # <---- New
另一種方法是使用 try/finally:
async def __aexit__(self, exc_type, exc_value, exc_tb):
try:
pass
print(exc_type)
print("Exit from the Context Manager...")
await asyncio.sleep(1)
finally:
print("This line is not executed") # <-------------------
uj5u.com熱心網友回復:
回答第一個問題:
假設如果我們在內部有一個 await 陳述句
__aexit__,那么該行之后的代碼根本不會執行是否正確?
我會說不,并非總是如此。只要main有足夠的時間并且可以再次將控制權傳遞回事件回圈,就__aexit__可以執行其中的代碼。我試過這個:
async def main():
async for i in get_numbers():
print(i)
if i == 1:
break
await asyncio.sleep(4) # <---- New
.run()只關心傳遞給它的協程并將其運行到最后,而不關心其他協程,包括__aexit__. 因此,如果它沒有足夠的時間或沒有將控制權傳遞給事件回圈,我就不能依賴第一個 await 陳述句之后的下一行。
可能有幫助的其他資訊:
在base_events.py /run_forever方法(由 呼叫.run())中,我發現它self._asyncgen_finalizer_hook被傳遞給了sys.set_asyncgen_hooks. 的主體_asyncgen_finalizer_hook是:
def _asyncgen_finalizer_hook(self, agen):
self._asyncgens.discard(agen)
if not self.is_closed():
self.call_soon_threadsafe(self.create_task, agen.aclose())
但是實作call_soon_threadsafe是空的。
我會清理這個答案并稍后洗掉這些猜測。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/498332.html
標籤:Python python-3.x 异步 蟒蛇异步 上下文管理器
上一篇:在chrome擴展的后臺腳本中,如何處理對chrome.runtime.onInstalled事件的addListener()方法的呼叫?
