以下代码死锁,而不是在异步任务中引发SystemExit
时退出。
import asyncio
import multiprocessing as mp
def worker_main(pipe):
try:
print("Worker started")
pipe.recv()
finally:
print("Worker exiting.")
async def main():
_, other_end = mp.Pipe()
worker = mp.Process(target=worker_main, args=(other_end,))
worker.daemon = False # (default), True causes multiprocessing explicitly to terminate worker
worker.start()
await asyncio.sleep(2)
print("Main task raising SystemExit")
raise SystemExit
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
它只打印以下内容,然后挂起:
Worker started
Main task raising SystemExit
如果然后按Ctrl+C,就会得到一个跟踪,指示它在multiprocessing.util._exit_function()
中等待p.join()
。
这是在Windows 10上使用Python3.9.5。
从表面上看,原因如下:
在异步任务中引发SystemExit
时,似乎所有任务都已结束,然后在事件循环之外重新引发SystemExit
。多处理库已向multiprocessing.util._exit_function()
注册了atexit.register()
,并被调用为主进程退出。这在辅助进程上执行p.join()
。关键的是,这发生在管道关闭之前,因此,当主进程等待工作人员退出时,它会与等待管道关闭(EOFError
)的工作人员死锁。
解决方案/解决方法似乎是使工作人员成为一个守护进程,以便_exit_function()
在打破死锁的p.join()
之前显式地终止它。唯一的问题是它阻止工人在退出前进行任何清理。
在非异步应用程序中也不会出现同样的问题,我不知道为什么会有所不同。如果应用程序是非异步的,那么当主进程退出时,工作人员的管道就会中断,并且工作人员将按预期的方式退出EOFError
。我还确认了,如果允许异步任务正常退出,但在run_until_complete()
返回后有一个run_until_complete()
,那么它的行为与非异步情况一样--也就是说,它正确地退出。
这是Python中的一个bug,还是预期它会这样做?
发布于 2022-02-24 05:19:12
Python3.10.0 + Win10具有相同的行为。
,这是Python中的一个bug,还是预期它会这样做?
问得好。在我看来,这不是Python中的一个bug。您有一个具有单个任务和一个线程的异步程序。启动事件循环后,主线程中的所有代码都在事件循环中运行。当它执行p.join()
时,它是一个阻塞调用。因为没有第二个线程可以导致取消块,所以程序就挂起了。
我的理解是,异步程序中的每个任务在(可能)传播异常(通常是到asyncio.run()调用)之前都会处理自己的异常。(至少这是我的经验,很难看出这是怎么回事。)因此,同步程序和异步程序之间的异常处理环境是不同的。
第二点: Python不可能知道如何优雅地终止非守护进程。如果将进程指定为守护进程,则实际上是告诉Python进程可以安全终止。然而,正如您所指出的,这将绕过任何所需的清理。
因为您有一个非守护进程,所以要由您的代码来执行清理。
第三点:如果您替换这一行:
raise SystemExit
有了这个:
raise Exception
同样的问题也会发生,原因也是一样的。程序不会退出,因为有一个非守护进程正在运行。这个问题不仅是SystemExit的清理,也是任何例外。如果你解决了这个问题,你的问题就没有意义了。
您可以在主函数中显式捕获SystemExit和异常,如下所示:
import asyncio
import multiprocessing as mp
def worker_main(pipe):
try:
print("Worker started")
pipe.recv()
finally:
print("Worker exiting.")
async def main():
my_end, other_end = mp.Pipe()
worker = mp.Process(target=worker_main, args=(other_end,))
try:
worker.daemon = False
worker.start()
await asyncio.sleep(2)
print("Main task raising SystemExit")
raise SystemExit
# raise Exception
except (SystemExit, Exception):
my_end.send("Bye")
worker.join()
raise
if __name__ == "__main__":
asyncio.run(main())
通过注释掉其中一个或另一个,您可以看到SystemExit和异常之间的区别。两人都优雅地离开了。SystemExit不会将追溯打印到控制台;异常会打印。
https://stackoverflow.com/questions/71236951
复制相似问题