我的工作线程设置如下:
from time import sleep
from threading import Event, Thread
class MyThread(Thread):
def __init__(self, *args, **kwargs):
# Following Doug Fort: "Terminating a Thread"
# (https://www.oreilly.com/library/view/python-cookbook/0596001673/ch06s03.html)
self._stop_request = Event()
super().__init__(*args, **kwargs)
def run(self):
while not self._stop_request.is_set():
print("My thread is running")
sleep(.1)
print("My thread is about to stop") # Finish my thread's job
def join(self, *args, **kwargs):
self._stop_request.set()
super().join(*args, **kwargs)
if __name__ == "__main__":
my_thread = MyThread()
my_thread.start()
sleep(2)
raise RuntimeError("Something went wrong!")
通过这个,我想实现以下目标:一旦主线程中发生任何未捕获的异常(如最后一行故意的
RuntimeError
),工作线程应该“完成其工作”(即运行行打印“我的线程即将停止”),然后也退出。
在实践中,会发生以下情况:
while
循环。什么是
更糟糕的是,提示对键盘中断没有反应,所以我必须
强行关闭提示窗口。使用
my_thread = MyThread(daemon=True)
似乎没有提供解决方案,因为它立即强制关闭工作线程,而不让它完成其工作。因此,Windows 上唯一的工作版本似乎是:一旦工作线程启动,将其他所有内容包装到一个 try–except
块中,因此:
if __name__ == "__main__":
my_thread = MyThread()
my_thread.start()
try:
sleep(2)
raise RuntimeError("Something went wrong!")
except:
my_thread.join()
然而,这看起来有点笨拙。另外,我不明白为什么它只在 Windows 上是必要的。我错过了什么吗?有更好的解决办法吗?
编辑: 在非 WSL Linux(Ubuntu 20.04 上的 Python 3.9)上,我遇到了与 Windows 下类似的行为;也就是说,工作线程在
RuntimeError
之后继续 - 但至少我可以在这里使用键盘中断。所以,这似乎不是仅限 Windows 的行为,但可能暗示我的期望是错误的(毕竟,没有人在原始设置中明确调用 my_thread.join()
,那么为什么要设置它的 _stop_request
呢? )。但我的根本问题仍然是一样的:如何让工作线程正常退出,如上所述?
我似乎找到了一个独立于系统的解决方案。但还是感觉有点笨拙:
import sys
from time import sleep
from threading import Event, Thread
# Monkey-patch ``sys.excepthook`` to set a flag for notifying child threads
# about exceptions in the main thread
exception_raised_in_main_thread = Event()
def my_excepthook(type_, value, traceback):
exception_raised_in_main_thread.set()
sys.__excepthook__(type_, value, traceback)
sys.excepthook = my_excepthook
class MyThread(Thread):
def run(self):
while not exception_raised_in_main_thread.is_set():
print("My thread is running")
sleep(.1)
print("My thread is about to stop")
if __name__ == "__main__":
my_thread = MyThread()
my_thread.start()
sleep(2)
raise RuntimeError("Something went wrong!")
sys.excepthook
(根据
文档,似乎没有误用 - 引用“可以通过将另一个三参数函数分配给sys.excepthook
来自定义此类顶级异常的处理”),我现在将设置一个事件,
exception_raised_in_main_thread
,通知所有子线程
关于主线程中发生的任何未捕获的异常。
我在问题中提出的内容和我最初提出的答案之间也存在某种“中间立场解决方案”:(1) 提供全局
exception_raised_in_main_thread
事件,(2) 将所有 __main__
代码包围起来一个 try-except
块并设置异常情况下的事件;因此:
from time import sleep
from threading import Event, Thread
exception_raised_in_main_thread = Event()
class MyThread(Thread):
def run(self):
while not exception_raised_in_main_thread.is_set():
print("My thread is running")
sleep(.1)
print("My thread is about to stop")
if __name__ == "__main__":
try:
my_thread = MyThread()
my_thread.start()
sleep(2)
raise RuntimeError("Something went wrong!")
except:
exception_raised_in_main_thread.set()
sys.excepthook
;因此不存在干扰其他潜在补丁的风险。在上面的代码片段中,我删除了另一个事件 (
self._stop_request
)为了简洁起见,但它仍然可能出现
在正常情况下可以方便地终止工作线程。