我使用QThread的方式是正确/最佳的吗?

时间:2018-01-06 18:29:42

标签: python multithreading pyqt5 qthread

您好我试图了解pyqt5中的线程。我概述了我目前使用的过程。这是正确的方法还是有更好的方法?注意,我是一个完整的穿线菜鸟。

这是我从GUI对象传递数据的方式。例如组合框已更改,我在GUI类中有一个self.comboBox.activated.connect(comboBoxChanged),然后相应的函数设置SharedVars.comboBoxChanged = True然后在其中一个threadloops中检查并查看SharedVars.comboBoxChanged = = True,如果是,则执行self.updateOtherStuffWhenComboBoxChanged.emit(getData())和SharedVars.comboBoxChanged = False

class SharedVars():
    ...

#This is my GUI class
class Ui_MainWindow(QThread, SharedVars):
    def setupUi(self, MainWindow):
        #self.table stuff

#this is the class i start threads, get a response and update the GUI
class MainUIClass(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super().__init__()
        self.setupUi(self)
        self.threadclass = ThreadClass()
        self.threadclass.start()
        self.threadclass.updateTable.connect(self.updateTable)
        self.threadclass2 = ThreadClass2()
        self.threadclass2.start()
        self.threadclass2.etc.connect(self.etc)


    def updateTable(self, val):
         #print("update self.table stuff")

    def etc(self, val):
        #print("update other stuff on gui")

#my first thread. If i have network calls that block i use several threads
class ThreadClass(QtCore.QThread, SharedVars):
    updateTable = pyqtSignal(list)
    def __init__(self, parent=None):
        super().__init__()

    def run(self):
        def getData():
            ...
            return [data]
        while True:
            self.updateTable.emit(getData())

#my second thread
class ThreadClass2(QtCore.QThread, SharedVars):
    etc = pyqtSignal(list)
    def __init__(self, parent=None):
        super().__init__()

    def run(self):
        def getData():
            ...
            return [data]
    while True:
        self.etc.emit(getData())

if __name__ == "__main__":
    a = QtWidgets.QApplication(sys.argv)
    app = MainUIClass()

    app.show()

    sys.exit(a.exec_())

也许有更好的方法。也许我可以直接从GUI对象发送信号到例如线程类。

编辑:好的,所以“three_pineapples”向我展示了一个例子,我转换为qt5,更改了一些术语,否则改变了一点。我也删除了装饰器,因为我无法让它们与qt5一起工作,我不知道这是不是一个坏主意(虽然它似乎仍然有效):

from PyQt5 import QtGui, QtWidgets, QtCore
from PyQt5.QtCore import pyqtSignal, QThread, QTimer

import time
import sys

class MyTask(QtCore.QObject):
    disableBtn = pyqtSignal()
    enableBtn = pyqtSignal()

    def firstTask(self):
        self.disableBtn.emit()
        print('oooh so much processing...')
        time.sleep(4)
        print('phew, finished!')
        self.enableBtn.emit()

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

        self.initUi()
        self.setupThread()

    def initUi(self):
        layout = QtWidgets.QVBoxLayout()
        self.button = QtWidgets.QPushButton('Click Me')
        layout.addWidget(self.button)
        self.setLayout(layout)
        self.show()

    def enableBtn(self):
        self.button.setEnabled(True)

    def disableBtn(self):
        self.button.setEnabled(False)

    def setupThread(self):
        self.thread = QThread()
        self.task = MyTask()

        self.task.moveToThread(self.thread)

        #self.thread.started.connect(self.task.firstTask)
        self.button.clicked.connect(self.task.firstTask)
        self.task.disableBtn.connect(self.disableBtn)
        self.task.enableBtn.connect(self.enableBtn)

        # Start thread
        self.thread.start()    

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    w = Window()
    app.exec_()

有人在您发布的链接中谈到了内存问题。我会经常从DB更新3个表元素,这是我需要担心的吗?另外,time.sleep()的用法。我听说有人不应该使用带有线程的time.sleep()吗?也许我可以使用QThread.msleep?

1 个答案:

答案 0 :(得分:0)

我不确定线程​​是否正确,但我非常有信心不需要它。 Qt的主要优势之一是基于事件。程序员连接各个组件之间的信号和插槽,以定义当某些事件发生时,您希望应用程序表现的方式。

在您发布的代码中,您正在运行这些后台线程,其唯一的工作似乎是充当GUI中事件(按钮点击)和其他逻辑之间的中间人(更新一些表,发网络请求)。只需将按钮单击时发出的信号直接连接到您想要的功能即可。

你有:

clicked --> thread makes network request --> thread emits signal with reply -->
    GUI receives data from thread signals and does some more work.

做类似的事情要简单得多:

clicked --> make network request --> respond to network reply

所有这一切都可能发生在主线程中,Qt将以异步方式执行所有网络I / O,以便不阻止应用程序的其余部分(例如GUI)。

例如,您可以更新主窗口以包含两个用于发出网络请求并处理响应的插槽,并将这些插槽直接连接到按钮单击信号。所以,它可能看起来像这样:

#!/usr/bin/env python3

import sys
from PyQt5 import QtWidgets, QtCore, QtNetwork

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.nam = QtNetwork.QNetworkAccessManager(self)
        self.push_button = QtWidgets.QPushButton("Make request", self)
        self.setCentralWidget(self.push_button)

        # When button is pressed, make network request
        self.push_button.clicked.connect(self.make_request)

        # When reply is received, do "work".
        self.nam.finished.connect(self.handle_reply)

    QtCore.pyqtSlot()
    def make_request(self):
        request = QtNetwork.QNetworkRequest(QtCore.QUrl("http://www.google.com"))
        self.nam.head(request)

    QtCore.pyqtSlot(QtNetwork.QNetworkReply)
    def handle_reply(self, reply):
        # This just prints the status code, but you can have
        # this slot update the GUI, change some data structure,
        # or emit another signal.
        print('Status code:',
                  reply.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute))
        reply.deleteLater()

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    win = MainWindow()
    win.show()
    sys.exit(app.exec_())

这都是假设您正在发出HTTP请求。如果您使用较低级别的协议而不是TCP,则需要QtNetwork.QTcpSocket,并且您使用QTcpSocket.readyRead()信号而不是{{1}信号。