gpt4 book ai didi

python - 在 PyQt5 中停止工作线程中的无限循环最简单的方法

转载 作者:行者123 更新时间:2023-12-03 08:19:57 25 4
gpt4 key购买 nike

我打算有一个 GUI,其中一个(后来的三个)线程以可调整的时间间隔(例如 10 秒)从不同的源读取实时数据,并在主窗口中绘制这些数据。

我正在使用 PyQt5 和 python 3.6。

读取是在工作线程中无限循环中执行的,如下所示:

class ReadingThread(QtCore.QObject):
output = QtCore.pyqtSignal(object)

def __init__(self, directory, interval):
QtCore.QObject.__init__(self)
self.directory=directory
self.stillrunning = True
self.refreshtime = interval


def run(self):
print('Entered run in worker thread')
self.stillrunning = True

while self.stillrunning:
outstring=self.read_last_from_logfile() # data reader function, not displayed

self.output.emit(outstring)
time.sleep(self.refreshtime)


@QtCore.pyqtSlot(int) # never called as loop is blocking?
def check_break(self, val):
if val:
self.stillrunning=False
else:
self.stillrunning = True

主线程如下所示,start() 和 stop() 通过按钮调用:

class Window(QtWidgets.QMainWindow, MainWindow.Ui_MainWindow):
def __init__(self, directory, interval):
super(Window, self).__init__()
self.thread = QtCore.QThread()
self.worker = ReadingThread(directory, interval)

emit_stop=QtCore.pyqtSignal(int)

def start(self):
self.worker.moveToThread(self.thread)

self.thread.started.connect(self.worker.run)
self.worker.output.connect(self.print_new_value)
self.emit_stop.connect(self.worker.check_break)

self.thread.start()

def stop(self):
self.emit_stop.emit(1)
# time.sleep(11)
if self.thread.isRunning(): #did not work either
self.thread.quit()

if self.thread.isRunning(): #did also not work
self.thread.terminate()
return

def print_new_value(self, value): #test function for output of values read by worker thread, working well
print (value)
return

def main():
app = QtWidgets.QApplication(sys.argv)
interval=10 #read every 10s new data
directory="/home/mdt-user/logfiles/T_P_logs"
gui = Window(directory,interval)
gui.show()
sys.exit(app.exec_())


if __name__ == '__main__':
main()

我的问题是:如何让循环中的工作线程查找主线程发出的传入信号?或者换句话说:我怎样才能有一个像我的 self.stillrunning 这样的状态变量,它可以在工作线程外部设置/访问,但在工作线程内检查?

我想避免使用诸如 self.thread.terminate() 之类的方法来终止线程,但我尝试过但没有成功。

非常感谢您的帮助。我当然进行了搜索,但给出的答案和/或提出的问题对于我认为必须是一个简单的解决方案来说要么太长,要么不适用。

最佳答案

我不明白上述评论如何解决您的问题。根据设计,您的工作线程将无法接收信号,因为 while 循环会阻止工作线程事件循环,直到它中断并且方法完成。然后所有(在整个阻塞期间)接收到的信号都将起作用。好吧,从技术上讲,您收到的信号并没有被阻止,它们只是没有发挥作用,直到事件循环再次发挥作用......
我看到两种适合您的设计模式的解决方案(利用移动到线程)。

解决方案 1:QTimer(干净、更像 QT 的解决方案)

这里的想法是使用 QTimer。您向该计时器提供一个时间段(以毫秒为单位),每次经过该时间段时,该计时器将执行一项任务(即调用方法/函数)。由于您甚至可以传递 0 毫秒作为时间段,因此您可以模拟类似 while 循环的行为。好处是:事件循环不会被阻塞,并且每次超时后,接收到的信号都会起作用。
我修改了你的代码,通过 QTimer 实现了这个解决方案。我认为有了代码注释,这个例子就有点不言自明了。

class ReadingThread(QtCore.QObject):

output = QtCore.pyqtSignal(object)

def __init__(self, directory, interval):
#QtCore.QObject.__init__(self)
super(ReadingThread, self).__init__() # this way is more common to me

self.directory = directory
self.refreshtime = interval

# setting up a timer to substitute the need of a while loop for a
# repetitive task
self.poller = QTimer(self)
# this is the function the timer calls upon on every timeout
self.poller.timeout.connect(self._polling_routine)

def _polling_routine(self):
# this is what's inside of your while loop i.e. your repetitive task
outstring = self.read_last_from_logfile()
self.output.emit(outstring)

def polling_start(self):
# slot to call upon when timer should start the routine.
self.poller.start(self.refreshtime)
# the argument specifies the milliseconds the timer waits in between
# calls of the polling routine. If you want to emulate the polling
# routine in a while loop, you could pass 0 ms...

def polling_stop(self):
# This simply stops the timer. The timer is still "alive" after.
self.poller.stop()

# OR substitute polling_start and polling_stop by toggling like this:
def polling_toggle(self):
poller_active = self.poller.isActive()
if poller_active:
# stop polling
self.poller.stop()
else:
# start polling
self.poller.start(self.refreshtime)


class Window(QtWidgets.QMainWindow, MainWindow.Ui_MainWindow):

emit_start = QtCore.pyqtSignal()
emit_stop = QtCore.pyqtSignal()

def __init__(self, directory, interval):
super(Window, self).__init__()
self.init_worker()

def init_worker(self):
self.thread = QtCore.QThread()

self.worker = ReadingThread(directory, interval)
self.worker.moveToThread(self.thread)

self.worker.output.connect(self.print_new_value)
self.emit_start.connect(self.worker.polling_start)
self.emit_stop.connect(self.worker.polling_stop)

self.thread.start()

def start_polling(self):
self.emit_start.emit()

def stop_polling(self):
self.emit_stop.emit()

def finish_worker(self):
# for sake of completeness: call upon this method if you want the
# thread gone. E.g. before closing your application.
# You could emit a finished sig from your worker, that will run this.
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)

def print_new_value(self, value):
print(value)

为了更好地了解如何使用 QThread 干净地完成此操作(这里的复杂性在于正确执行线程,QTimer 相对微不足道):https://realpython.com/python-pyqt-qthread/#using-qthread-to-prevent-freezing-guis
编辑:请务必查看 QTimer 的文档。您可以动态设置超时时间等等。

解决方案 2:传递可变参数作为控制变量

您可以例如将带有控制变量的字典传递到您的工作类/线程中,并使用它来打破循环。这是可行的,因为(下面是过于简化的语句)线程共享公共(public)内存,并且 python 中的可变对象共享内存中的同一对象(这已经在 SO 上进行了深入讨论)。我将在修改后的代码中对此进行说明,同时说明您会发现主线程和工作线程中的控制指令的内存 ID 相同:

class ReadingThread(QtCore.QObject):
output = QtCore.pyqtSignal(object)

def __init__(self, directory, interval, ctrl):
QtCore.QObject.__init__(self)
self.ctrl = ctrl # dict with your control var
self.directory = directory
self.refreshtime = interval

def run(self):
print('Entered run in worker thread')
print('id of ctrl in worker:', id(self.ctrl))
self.ctrl['break'] = False

while True:
outstring=self.read_last_from_logfile()
self.output.emit(outstring)

# checking our control variable
if self.ctrl['break']:
print('break because flag raised')
# might emit finished signal here for proper cleanup
break # or in this case: return

time.sleep(self.refreshtime)


class Window(QtWidgets.QMainWindow, MainWindow.Ui_MainWindow):

emit_stop=QtCore.pyqtSignal(int)

def __init__(self, directory, interval):
super(Window, self).__init__()
self.thread = QtCore.QThread()
self.ctrl = {'break': False} # dict with your control variable
print('id of ctrl in main:', id(self.ctrl))

# pass the dict with the control variable
self.worker = ReadingThread(directory, interval, self.ctrl)

def start(self):
self.worker.moveToThread(self.thread)

self.thread.started.connect(self.worker.run)
self.worker.output.connect(self.print_new_value)

self.thread.start()

def stop(self):
# we simply set the control variable (often refered to as raising a flag)
self.ctrl['break'] = True

这个解决方案几乎不需要改变你的代码,我肯定会认为它不像QT,甚至不干净,但它非常方便。有时您不想围绕您使用 GUI 工具包的事实来编写实验/长时间运行的任务。
这是我所知道的唯一方法,可以让你绕过阻塞的事件循环。如果有人对此有更清洁的解决方案,请让全世界知道。特别是因为这是以受控方式从多个点突破长时间运行的任务的唯一方法,因为您可以在整个重复例程中多次检查控制变量。

关于python - 在 PyQt5 中停止工作线程中的无限循环最简单的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68163578/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com