我无法退出我的Python应用。在调用sys.exit()之后,python.exe继续运行,我必须使用任务管理器将其终止。
过去4个小时我一直在研究这个问题,而且我很难过。
这是Windows 10 x86上的Python 3.4.4。
首先,我有一个多线程应用程序。但是,在调用sys.exit()之前,我已经验证所有线程都在退出,只有主线程在运行。 (我这样做是通过在while循环中调用threading.enumerate()并等到只剩下主线程,打印正在运行的线程列表并观察它在每个循环中变小,直到只剩下主线程。 )
另外,我已经确认我没有在try:block中包含任何吞噬SystemExit异常的块。如果我打印sys.exc_info()我得到(None,None,None),如果我调用raise,那么它也确认没有异常待处理。
有趣的是,我通过评论我的应用程序的不同部分逐一禁用每个帖子,将其缩小到违规线程。 (我总共有4个帖子,每个都做不同的事情。)
如果我评论出有问题的帖子,我可以退出我的应用程序没问题。但同样,即使我运行该线程,该线程也成功退出,那里只有一些阻止主要Python exe退出的东西。
我已尝试设置守护程序标志,但这样做无论如何都无法做到。违规线程的目的是在PriorityQueue()等待1秒超时,然后当超时时检查threading.Event()标志以正常退出自身。再次,这很好。
,我可以在我的while()循环中看到该程序正在退出该线程正在运行,然后停止。唯一的其他信息是此应用程序是通过console_scripts条目启动的。我查看了setuptools创建的脚本文件,看到它只是将调用包装到sys.exit()中的入口点,但即使是黑客攻击该文件,我也无法退出。
我试过调用sys.exit,引发SystemExit,然后返回让console_script调用sys.exit。这些都不起作用。
我也尝试过更多暴力行为,比如os._exit(),但这也没有用。
真正奇怪的是,如果我创建一个递归循环(一个只调用自身的简单单行方法),并且在我设置阻止线程的线程事件之前将其放在我的stop方法中,然后Python将按原样退出。 (我错误地做了那个,并且首先是傻眼了,但是如果我在调用sys.exit之前将那个循环调用了几行,那么递归循环不会杀死python.exe。所以即使我的问题线程正常退出,有关它试图退出的事情导致Python.exe挂起。
所以,我的问题是,有没有人有任何其他的想法或事情可以尝试Python为什么不退出?特别是为什么我的问题线程停止,只有主线程仍然存在,但sys.exit或os._exit()什么都不做?我完全难过了。
我的应用程序消耗大约90MB的内存,在任务管理器中,我可以看到GC正在完成其工作,就像我的应用程序是"挂起"在sys.exit()调用之后,我看到内存使用量在大约30秒的时间内从90MB下降到0.1MB。但即使离开它之后,python.exe也不会停止。
更新:这里有一些代码,用于演示内容:
从注册为console_script的模块和功能:
def run_from_command_line(args=None):
path = os.path.abspath(os.path.curdir)
CommandLineUtility(path).execute()
从启动我的应用程序的CommandLineUtility()。这是最后一行:
def __init__(...):
... skipping a bunch of setup stuff
MpfMc(options=vars(args), config=mpf_config,
machine_path=machine_path).run() # this is not a threading run, just the name of the method for my app
来自MpfMc():
def __init__(...):
...
self.thread_stopper = threading.Event()
...
self.asset_manager = AssetManager(self)
来自AssetManager():
self.loader_thread = AssetLoader(loader_queue=self.loader_queue,
loaded_queue=self.loaded_queue,
exception_queue=self.machine.crash_queue,
thread_stopper=self.machine.thread_stopper)
self.loader_thread.daemon = True
self.loader_thread.start()
来自AssetLoader:
def run(self):
"""Run loop for the loader thread."""
while True:
try:
asset = self.loader_queue.get(block=True, timeout=1)
except Empty:
asset = None
if self.thread_stopper.is_set():
return
if asset:
if not asset.loaded:
with asset.lock:
asset.do_load()
self.loaded_queue.put(asset)
从停止app的MpfMc.stop()方法:
def stop(self):
self.log.info("Stopping ...")
self.thread_stopper.set()
while [x for x in self.threads if x.is_alive()]:
# self.threads is a list of threads I created, not the main thread.
print("Waiting for threads to stop")
print([x for x in self.threads if x.is_alive()])
print(threading.enumerate())
time.sleep(0.5)
for thread in self.threads:
# verify none of the sub threads are alive
print("THREAD", thread, thread.is_alive())
sys.exit() # here's where I also tried raise SystemExit, os._exit(), etc
谢谢!