QThread: 在退出时线程仍在运行中被销毁。

6

我正在寻找一种正确退出我的应用程序的方法。当我退出时,会出现一个错误,提示QThread: Destroyed while thread is still running。我有一个线程用于将输出发送到QTextBrowser。正确的退出方式应该是什么?以下是我的代码:

class LogReceiver(QtCore.QObject):
    mysignal = QtCore.Signal(str)

    def __init__(self, queue, *args, **kwargs):
        QtCore.QObject.__init__(self, *args, **kwargs)
        self.queue = queue

    def run(self):
        while True:
            text = self.queue.get()
            self.mysignal.emit(text)

if __name__ == '__main__':
    queue = Queue()
    thread = QtCore.QThread()
    my_receiver = MyReceiver(queue)

    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()

    my_receiver.mysignal.connect(window.append_text)
    my_receiver.moveToThread(thread)
    thread.started.connect(my_receiver.run)
    thread.start()

    sys.exit(app.exec_())

在退出时,thread是否应该被终止?请注意,self.queue.get()会阻塞并等待文本。

谢谢

2个回答

3
你需要重新构造while循环,这样它就不会无条件地阻塞。
你可以通过一个简单的标志和超时来实现这一点:
    def run(self):
        self.active = True
        while self.active:
            try:
                text = self.queue.get(timeout=1.0)
                self.mysignal.emit(text)
            except Empty:
                continue

现在队列不会无限制地阻塞,每秒钟检查一次标志以确定是否应该退出循环。
编辑:这是基于您的代码的可工作示例。
import sys
from queue import Queue, Empty
from PySide import QtCore, QtGui

class LogReceiver(QtCore.QObject):
    mysignal = QtCore.Signal(str)

    def __init__(self, queue, *args, **kwargs):
        QtCore.QObject.__init__(self, *args, **kwargs)
        self.queue = queue

    def run(self):
        self.active = True
        while self.active:
            try:
                text = self.queue.get(timeout=1.0)
                self.mysignal.emit('text')
            except Empty:
                continue
        print('finished')

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.queue = Queue()
        self.thread = QtCore.QThread(self)
        self.receiver = LogReceiver(self.queue)
        self.receiver.moveToThread(self.thread)
        self.thread.started.connect(self.receiver.run)
        self.thread.start()

    def closeEvent(self, event):
        print('close')
        self.receiver.active = False
        self.thread.quit()
        self.thread.wait()

if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)
    window = MainWindow()
    window.show()

    sys.exit(app.exec_())

使用prajmus的第二个解决方案,并搭配参数 timeout=1.0,但是在 thread.wait() 处会卡住。 - Kar
@Kate。我认为这些解决方案都不会起作用,因为它们尝试在主事件循环终止后对线程进行操作。我已经添加了一个工作示例到我的答案中,以展示如何使用我的解决方案。 - ekhumoro

2

尝试:

# previous code here
thread.start()
app.exec_()

thread.terminate()
thread.wait()
sys.exit(0)

基本上,当exec_()完成(例如通过关闭窗口关闭QApplication)时,您会强制终止thread并等待其清理。如果您的线程有一个事件循环,可以调用quit()而不是terminate()。一般情况下,terminate()不是一个好主意,请参见:这里

更理想的方法是在run()方法中放置一个标志,例如:

while !flag:
    do stuff

并将主要内容更改为:

app.exec_()
flag = True
thread.wait()
sys.exit(0)

其中flag是全局变量。QThreadrun()方法完成时自行终止。


谢谢。我不确定是否可以运行thread.wait(),因为线程正在阻塞。我已经更新了我的问题,说明了它是如何被阻塞的。 - Kar
我理解第二种解决方案(使用标志位)的方式是,run()需要达到while条件才能打破循环。如果在设置flag = Truetext = self.queue.get()已经被阻塞,线程仍将无限期地被阻塞。 - Kar
@Kate 是的,你说得对,抱歉我打字比想象中快了 :) - prajmus
1
@Kate,你能以非阻塞的方式完成吗?比如说使用 while flag: while queue is not empty 获取文本并发出信号(第二个 while 在你已经有的那个 while 里面)。 - prajmus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接