繁体   English   中英

在主线程异常上优雅地退出子线程

[英]Gracefully exiting a child thread on main thread exception

我有一个工作线程设置,如下所示:

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 ),工作线程应该“完成它的工作”(即运行打印“我的线程”的行即将停止”),然后也退出。

在实践中,会发生以下情况:

  • 在 Linux 终端(Debian WSL 上的 Python 3.5)上,这按预期工作。
  • 然而,在 Windows PowerShell 或命令提示符(Windows 10 上的 Python 3.7)上,工作线程继续运行,从不退出其while循环。 更糟糕的是,提示对键盘中断没有反应,所以我不得不强行关闭提示window。

使用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 (根据文档,这似乎没有滥用),我现在将设置一个事件exception_raised_in_main_thread ,以通知所有子线程有关主线程中发生的任何未捕获的异常。

请注意,在上面的代码片段中,为简洁起见,我删除了另一个事件( self._stop_request ),但在正常情况下它可能仍可用于终止工作线程。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM