【问题标题】:QThread doesn't finishQThread 没有完成
【发布时间】:2015-12-23 00:49:53
【问题描述】:

我正在构建一个带有在 PyQt5 的后台线程中运行的套接字连接的 GUI。除了 Qthread 从不发出完成的信号外,一切都运行良好。也许这不是问题,我可以改变我的实现来解决它,但是一旦已移动的对象停止执行任何操作,Qthread 将继续运行的预期行为吗?

我应该在主类中编写一个函数以在我完成后停止线程,还是我可以将新内容移至该线程而不产生任何后果?

class MyClass(PyQt5.QtWidgets.QMainWindow)
    def __init__(self, parent=None):

        # Setup thread for the socket control
        self.simulThread = PyQt5.QtCore.QThread()
        self.socketController = SocketController()
        self.socketController.moveToThread(self.simulThread)
        self.simulThread.started.connect(self.socketController.controlSocket)

        self.simulThread.finished.connect(self.deadThread)

        # Bind controls to events
        self.ui.buttonConnect.clicked.connect(self.simulThread.start)
        self.ui.buttonDisconnect.clicked.connect(lambda: self.socketController.stop())

   def deadThread(self, data):
       print ("THREAD IS DEAD.")

class SocketController(PyQt5.QtCore.QObject):
    finished       = PyQt5.QtCore.pyqtSignal()

    def __init__(self):
        super(SocketController, self).__init__()
        self.run = True;

    def controlSocket(self):
        #setup the socket

        while self.run:
            # do socket stuff
            time.sleep(1)

        #close the socket
        self.finished.emit()

    def stop(self):
        self.run = False;

【问题讨论】:

  • 你的意思是 THREAD IS DEAD 被打印出来了吗?
  • @Schollii 它确实不会被打印出来。

标签: python python-3.x pyqt pyqt5


【解决方案1】:

QThread 有自己的事件循环来处理信号。 controlSocket 方法阻止此事件循环,直到 self.run == False。但这永远不会发生,因为 self.run 仅在控制权返回到事件 lop 时设置为 False 并且它可以处理运行 stop 方法的信号。

您可能想要重新架构您的线程,以便在每 1 秒调用一次 #do socket stuff 代码的线程中构造一个 QTimer,而不是使用阻塞 QThread 事件循环的 while 循环。这样,控制权返回到线程事件循环,它可以处理停止信号(将更改为停止QTimer再次触发)

【讨论】:

  • 你说的是有道理的,但是这一行:self.ui.buttonDisconnect.clicked.connect(lambda: self.socketController.stop()) 似乎实际上打破了我的while循环(线程打印出for循环之后出现的消息并且套接字断开连接。)所以我相信函数正在返回,但线程没有完成。
  • 我为 SocketController 类创建的完成信号也被断言,但不是来自 simulThread 的完成信号。
  • @Ian 哦,stop 方法实际上是在主线程中运行的,因为您将连接包装在 lambda 中(因此 Qt 的魔法确定在哪个线程中运行某些东西并没有'不起作用,因为 lambda 始终存在于创建它们的线程中)。所以我不认为我有答案......
【解决方案2】:

答案实际上是three_pineapples和OP在他们的答案中发布的组合:

  1. 正如three_pineapples 所指出的,主要问题是在发出started 信号时由线程事件循环调用的controlSocket() 直到调用stop() 方法才返回。但是,在 OP 的设计中,只有当套接字线程可以处理事件时,Qt 才能调用 stop() 方法(因为这是通过事件循环分派跨线程信号的方式)。在 controlSocket() 忙于循环和休眠时,这是不可能的。
  2. 为了让线程退出,它的事件循环必须停止。

第一个问题必须通过允许线程在循环期间处理事件或使用计时器而不是循环来解决。处理事件显示在这段代码中,基于 OP 代码:

import time

from PyQt5.QtWidgets import QMainWindow, QWidget, QPushButton, QApplication, QHBoxLayout
from PyQt5.QtCore import QThread, QObject, pyqtSignal

class MyClass(QWidget):
    def __init__(self, parent=None):
        super().__init__()

        self.resize(250, 150)
        self.setWindowTitle('Simple')

        # Setup thread for the socket control
        self.socketController = SocketController()

        self.simulThread = QThread()
        self.socketController.moveToThread(self.simulThread)
        self.simulThread.started.connect(self.socketController.controlSocket)
        self.simulThread.finished.connect(self.deadThread)

        # Bind controls to events
        self.buttonConnect = QPushButton('connect')
        self.buttonConnect.clicked.connect(self.simulThread.start)

        self.buttonDisconnect = QPushButton('disconnect')
        self.buttonDisconnect.clicked.connect(self.socketController.stop)

        hbox = QHBoxLayout()
        hbox.addStretch(1)
        hbox.addWidget(self.buttonConnect)
        hbox.addWidget(self.buttonDisconnect)
        self.setLayout(hbox)

    def deadThread(self, data):
        print("THREAD IS DEAD.")


class SocketController(QObject):
    finished = pyqtSignal()

    def __init__(self):
        print('initialized')
        super().__init__()
        self.run = True

    def controlSocket(self):
        # setup the socket

        print('control socket starting')
        while self.run:
            # do socket stuff
            app.processEvents()
            print('control socket iterating')
            time.sleep(1)

        # close the socket
        self.finished.emit()
        print('control socket done')

    def stop(self):
        print('stop pending')
        self.run = False


app = QApplication([])
mw = MyClass()
mw.move(300, 300)
mw.show()
app.exec()

QTimer 稍微复杂一些,但想法是让 controlSocket() 只做一次循环迭代,并通过 QTimer 重复调用它:

QTimer.singleShot(0, self.controlSocket)

def controlSocket(self):
    # do socket stuff, then:
    if self.run:
        QTimer.singleShot(1000, self.controlSocket)
    else:
        self.finished.emit()

上述任何一种方法都解决了第一个问题,并允许SocketController 停止执行其与套接字相关的工作。

第二个问题是,即使套接字控制器完成了它的工作,线程循环仍然在运行(两个循环是独立的)。要使事件循环退出,必须通过线程的 quit() 方法将其停止。退出和停止应该一起完成:

class MyClass(QWidget):
    def __init__(self, parent=None):
        ...

        self.buttonDisconnect = QPushButton('disconnect')
        self.buttonDisconnect.clicked.connect(self.stop)

        hbox = QHBoxLayout()
        hbox.addStretch(1)
        hbox.addWidget(self.buttonConnect)
        hbox.addWidget(self.buttonDisconnect)
        self.setLayout(hbox)

    def stop(self):
        self.simulThread.quit()
        self.socketController.stop()

【讨论】:

    【解决方案3】:

    好的,所以我找到了一个解决方案,但我真的不知道它是否正确。我没有将断开连接按钮映射到套接字停止函数,而是将其映射到 MyClass 中定义的函数,该函数调用我编写的名为 controlDisconnect 的新函数,该函数还显式地告诉线程退出。

    # The changed line
    self.ui.buttonDisconnect.clicked.connect(self.controlDisconnect)
    
    def controlDisconnect(self):
        self.socketController.stop()
        self.simulThread.quit()
    

    虽然这是有效的(线程打印出表明它已死亡的消息),但我不确定它是否真的很好。至少应该有一些代码来确保 socketController 在告诉线程退出之前实际上已经停止。

    【讨论】:

    • 这里有些奇怪。是否调用了 controlSocket() 方法?由于它有一个只有在调用 stop() 时才停止的无限循环,所以我希望这个方法在 stop() 之前永远不会返回,但是 stop() 只能由 controlDisconnect() 调用,只有在线程正在处理事件,如果它陷入无限循环,则不会发生......注意:您的答案(或您的问题,但您必须删除调用括号( ))。
    • @Schollii,感谢您对 lambda 的了解,这是我还没有完全理解整个线程设置的神器。无论如何,是的,它 controlSocket 绑定到线程启动事件。从我上面发布的内容可能不清楚,但 controlDisconnect 在主窗口中,而不是线程类。
    • 也许我误解了一些东西,但 controlDisconnect 是 MyClass 的一部分,并由按钮按下事件调用。所以它的执行是独立于线程的。
    猜你喜欢
    • 1970-01-01
    • 2012-04-01
    • 2015-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多