繁体   English   中英

QThread没有完成

[英]QThread doesn't finish

我正在使用在PyQt5中的backround线程中运行的套接字连接构建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;

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

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

答案实际上是three_pineapples和OP在其答案中张贴的内容的组合:

  1. 正如three_pineapples指出的那样,主要问题在于,在发出start信号时,线程事件循环调用的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()

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

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

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

当此方法有效时(该线程会打印出表明其已死亡的消息),但我不确定它是否是真正的好习惯。 在告诉线程退出之前,可能至少应该有一些代码来确保socketController实际上已经停止。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM