Stop processing event-queue immediately on QThread.exit()
我正在构建一个Qt GUI应用程序,该应用程序使用QThread / QObject组合充当在主线程之外执行工作的工作程序。
通过
现在,我想让工作人员以一种特殊的方式行事,以便每当事件循环中的某个插槽遇到Python异常时,他们都会优雅地停止线程。
通过一些测试,我发现在PyQt5中,插槽中的异常会导致整个应用程序停止运行,据我所知,这是与PyQt4相比的有意更改,在PyQt4中,仅打印了例外,但事件循环一直运行。我读到,可以通过将您自己的" excepthook"猴子修补到
所以我做到了,到目前为止,这是可行的。此外,当发生异常时,excepthook使我能够
以下是展示我到目前为止所拥有的MWE。我的问题是,发生异常时,退出线程不会立即发生,如
我该如何解决?有没有一种方法可以清除
还是另一种完全不同的方式来捕获插槽中的异常并使线程停止而不停止主程序?
码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | import sys import time from qtpy import QtWidgets, QtCore class ThreadedWorkerBase(QtCore.QObject): def __init__(self): super().__init__() self.thread = QtCore.QThread(self) self.thread.setTerminationEnabled(False) self.moveToThread(self.thread) self.thread.start() def schedule(self, slot, delay=0): """ Shortcut to QTimer's singleShot. delay is in seconds.""" QtCore.QTimer.singleShot(int(delay * 1000), slot) class Worker(ThreadedWorkerBase): test_signal = QtCore.Signal(str) # just for demo def do_work(self): print("starting to work") for i in range(10): print("working:", i) time.sleep(0.2) def error(self): print("Throwing error") raise Exception("This is an Exception which should stop the worker thread's event loop.") # set excepthook to explicitly exit Worker thread after Exception sys._excepthook = sys.excepthook def excepthook(type, value, traceback): sys._excepthook(type, value, traceback) thread = QtCore.QThread.currentThread() if isinstance(thread.parent(), ThreadedWorkerBase): print("This is a Worker thread. Exiting...") thread.exit() sys.excepthook = excepthook # create demo app which schedules some tasks app = QtWidgets.QApplication([]) worker = Worker() worker.schedule(worker.do_work) worker.schedule(worker.error) # this should exit the thread => no more scheduling worker.schedule(worker.do_work) worker.thread.wait() # worker should exit, just wait... |
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | starting to work working: 0 working: 1 working: 2 working: 3 working: 4 working: 5 working: 6 working: 7 working: 8 working: 9 Throwing error Traceback (most recent call last): File"qt_test_so.py", line 31, in error raise Exception("This is an Exception which should stop the worker thread's event loop.") Exception: This is an Exception which should stop the worker thread's event loop. This is a Worker thread. Exiting... starting to work working: 0 working: 1 working: 2 working: 3 working: 4 working: 5 working: 6 working: 7 working: 8 working: 9 |
期望:
输出应在" Exiting ..."之后结束。
Tells the thread's event loop to exit with a return code.
After calling this function, the thread leaves the event loop and
returns from the call to QEventLoop::exec(). The QEventLoop::exec()
function returns returnCode.By convention, a returnCode of 0 means success, any non-zero value
indicates an error.Note that unlike the C library function of the same name, this
function does return to the caller -- it is event processing that
stops. [emphasis added]
这表明在调用
在您的示例中,单次计时器会将事件发布到接收线程的事件队列中,最终将在该事件队列中调用连接的插槽。 因此,无论您做什么,所有这些插槽都会在线程最终退出之前被调用。
解决此问题的一种非常简单的方法是将
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | def interruptable(slot): def wrapper(self, *args, **kwargs): if not self.thread.isInterruptionRequested(): slot(self, *args, **kwargs) return wrapper class Worker(ThreadedWorkerBase): test_signal = QtCore.pyqtSignal(str) # just for demo @interruptable def do_work(self): print("starting to work") for i in range(10): print("working:", i) time.sleep(0.2) @interruptable def error(self): print("Throwing error") raise Exception("This is an Exception which should stop the worker thread's event loop.") def excepthook(type, value, traceback): sys.__excepthook__(type, value, traceback) thread = QtCore.QThread.currentThread() if isinstance(thread.parent(), ThreadedWorkerBase): print("This is a Worker thread. Exiting...") thread.requestInterruption() thread.exit() sys.excepthook = excepthook |