QThread没有完成

时间:2015-12-23 00:49:53

标签: python python-3.x pyqt pyqt5

我正在使用在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;

3 个答案:

答案 0 :(得分:0)

QThread有自己的事件循环来处理信号。 controlSocket方法阻止此事件循环,直到self.run == False。但是这种情况从未发生过,因为当{1}}仅在控件返回到事件lop时设置为self.run并且它可以处理运行False方法的信号。

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

答案 1 :(得分:0)

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

  1. 正如three_pineapples所指出的,主要的问题是controlSocket(),当启动信号时被线程事件循环调用,在调用stop()方法之前不会返回。但是,在OP的设计中,如果套接字线程可以处理事件,则只能通过Qt调用stop()方法(因为这是通过事件循环调度跨线程信号的方式)。当controlSocket()忙于循环和休眠时,这是不可能的。
  2. 为了让线程退出,必须停止其事件循环。
  3. 第一个问题必须通过允许线程在循环期间处理事件,或者通过使用计时器而不是循环来修复。处理事件显示在这段代码中,基于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()
    

答案 2 :(得分:-1)

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

# The changed line
self.ui.buttonDisconnect.clicked.connect(self.controlDisconnect)

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

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