Я создаю приложение Qt GUI, которое использует комбинации QThread / QObject, чтобы действовать как рабочие, которые делают вещи вне основного потока.
Через moveToThread
объект QObject перемещается в QThread. Таким образом, мой работник может иметь сигналы (это QObject) и слоты, которые обрабатываются в цикле событий (предоставляется QThread).
Теперь я хотел бы заставить рабочих вести себя особым образом, чтобы они грациозно останавливали свой поток всякий раз, когда слот в цикле событий попадает в исключение Python.
Проведя небольшое тестирование, я обнаружил, что в PyQt5 исключение в слоте вызывает остановку всего приложения, что, насколько я понимаю, является преднамеренным изменением по сравнению с PyQt4, где исключение было только напечатано, но цикл событий сохранился. Бег. Я читал, что этого можно избежать, установив собственное «exchook» на sys.excepthook
, которое Qt реализует таким образом, что оно останавливает интерпретатор.
Итак, я сделал это, и пока это работает. Кроме того, исключающий крюк позволяет мне exit()
моего работника, когда происходит Исключение, для которого я не нашел лучшего способа в другом месте. Я попытался создать подкласс QThread и поместить try..except
вокруг вызова exec_()
в методе QThread run()
, но он не распространяет исключения, возникающие в цикле событий ... Таким образом, единственной оставшейся опцией было бы поставить try..except
блоков внутри каждого слота, которого я хотел избежать. Или я что-то здесь упустил?
Ниже приведен MWE, демонстрирующий то, что у меня есть до сих пор. Моя проблема с этим заключается в том, что выход из потока не происходит сразу же, когда возникает исключение, продемонстрированное с помощью слота error
, что приводит к вызову thread.exit()
в exchook. Вместо этого будут выполняться все остальные оставшиеся события в цикле событий потока, что демонстрируется слотом do_work
, который я запланировал за ним. exit()
просто, кажется, планирует другое событие в очереди, которое, как только оно обрабатывается, затем останавливает цикл обработки событий.
Как я могу обойти это? Есть ли способ очистить очередь событий QThread
? Можно ли как-то расставить приоритеты при выходе?
Или, может быть, другой совершенно другой способ перехвата исключений в слотах и остановки потока без остановки основной программы?
Код :
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...
выход
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
Expectation
Выход должен заканчиваться после «Exiting ...».