【问题标题】:PyQt5 signal communication between worker thread and main window is not working correctly工作线程和主窗口之间的 PyQt5 信号通信无法正常工作
【发布时间】:2019-03-29 02:39:27
【问题描述】:

我正在使用 PyQt5 创建一个应用程序,它有一个非常简单的用户界面。有一个下拉列表,可以从中选择一个值,然后有一个可以单击的按钮,将根据下拉列表中的当前值执行操作。

目前,如果我运行下面的代码,我可以从下拉列表中获取一个值,它将在myaction.myaction(customer_name) 运行之前发送到工作线程。代码也运行良好,但是当工作线程中的函数运行时,GUI 没有按照我希望的方式工作。当我单击 start 按钮时,它应该向 GUI 发出一个信号以禁用该按钮,更改其标签和颜色,但这从未发生过。函数完成后,应将其改回原来的形式。

问题是我如何处理信号,还是我的课程中有多余的功能?每次单击按钮时将该下拉列表值发送到工作线程的正确方法是什么,所以我可以简单地将它用作那里的变量?

我在网上找到的每一个可能的解决方案都让我很兴奋,但没有一个对我有用,或者其中一些让我太困惑而无法理解。

这是我已经看过的一些答案


#!/usr/bin/env python3

import sys
#import myaction
import time
from PyQt5.QtWidgets import QWidget, QApplication, QPushButton, QComboBox, QLabel
from PyQt5 import QtCore

class ConfWorker(QtCore.QThread):
    updated_button = QtCore.pyqtSignal(list)
    updated_label = QtCore.pyqtSignal(str)
    updated_error = QtCore.pyqtSignal(str)
    request_signal = QtCore.pyqtSignal()
    customer = QtCore.pyqtSignal(str)

    def __init__(self, parent=None):
        super(ConfWorker, self).__init__(parent)
        self.customer.connect(self.getcustomer)

    def run(self):
        self.request_signal.emit()

    def getcustomer(self, text):
        self.configure(text)

    def configure(self, customer_name):
        self.updated_button.emit(["In progress...", False])
        self.updated_label.emit(customer_name)
        time.sleep(5) # During this time you should be able to see color change etc.
        #myaction.myaction(customer_name)# TAKES ~20 SECONDS TO FINISH
        self.updated_button.emit(["Start", True])

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

        self.worker = ConfWorker(self)
        self.worker.updated_button.connect(self.updateButton)
        self.worker.updated_label.connect(self.updateLabel)
        self.worker.updated_error.connect(self.updateError)
        self.worker.request_signal.connect(self.sendCustomer)

        self.targetBtn = QPushButton('Start Configuration', self)
        self.targetBtn.setStyleSheet("QPushButton { background-color: green; color: white }"
                        "QPushButton:disabled { background-color: red; color: white }")
        self.targetBtn.clicked.connect(self.worker.start)
        self.targetBtn.setGeometry(100, 400, 200, 50)
        self.setGeometry(800, 300, 400, 550)
        self.setFixedSize(400, 550)

        self.customerlist = QComboBox(self)
        self.customerlist.setGeometry(100, 50, 200, 50)
        self.customerlist.setObjectName("customer")
        self.customerlist.addItem("testcustomer1")
        self.customerlist.addItem("testcustomer2")
        self.customerlist.addItem("testcustomer3")

        self.label = QLabel(self)
        self.label.setText("")
        self.label.setStyleSheet('font-size: 30pt; font-family: Courier; color: green;')
        self.label.setGeometry(70,250,400,50)

        self.error_label = QLabel(self)
        self.error_label.setText("")
        self.error_label.setStyleSheet('font-size: 30pt; font-family: Courier; color: red;')
        self.error_label.setGeometry(70,350,400,50)

        self.show()

    def sendCustomer(self):
        self.worker.customer.emit(self.customerlist.currentText())

    def updateButton(self, button_list):
        self.targetBtn.setText(button_list[0])
        self.targetBtn.setEnabled(button_list[1])

    def updateLabel(self, label_text):
        self.label.setText(label_text)

    def updateError(self, error_text):
        self.error_label.setText(error_text)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = ConfGUI()
    sys.exit(app.exec_())

【问题讨论】:

  • 可以分享模块myaction.py
  • @S.Nick 你是什么意思?
  • 导入我的动作???
  • @S.Nick 哦,抱歉,这只是我自己的模块,我不允许分享!如果你想测试它,你可以用time.sleep(n) 替换它,并且可能将customer_name 发送到GUI。同样的问题仍然存在。我进行了更改,因此它可以在不导入模块的情况下运行。

标签: python multithreading pyqt pyqt5 qthread


【解决方案1】:

问题是由一个很常见的错误概念引起的,他们认为QThread是一个Qt Thread,也就是Qt做的一个新线程,但是不是,QThread是一个线程handlerthe docs:

QThread 类提供了一种独立于平台的管理方式 线程。

QThread 对象管理程序中的一个控制线程。 QThreads 在 run() 中开始执行。默认情况下,run() 启动事件 通过调用 exec() 循环并在线程内运行 Qt 事件循环。

在另一个线程上运行的唯一部分是 run 方法,在您的情况下,您没有调用,因为您的逻辑不同,您不想连续执行繁重的任务而是应用户的要求,因此设计必须是一个工人,但您使用 QThread 作为基类,这是不正确的,您必须使用 QObject 作为基类并将其移动到新线程,以便 QObject 在该线程中执行其任务,从而阻止 GUI从阻塞。

QObject 不是线程安全的,它存在于一个线程中,而它所在的线程由以下内容决定:

  • QObject 存在于父线程中

  • 如果您没有父级,则住在创建它的线程中,除非您已移至另一个线程。

  • 您可以使用moveToThread() 函数移动到另一个线程,但如果QObject 有父级,则moveToThread() 将失败,因为第一个条件是特权。

另一方面,如果你想从另一个线程调用一个方法,你必须使用装饰器@QtCore.pyqtSlot()


综合以上,我们得到如下解:

#!/usr/bin/env python3
import sys
import time
from PyQt5 import QtCore, QtWidgets


class ConfWorker(QtCore.QObject):
    updated_button = QtCore.pyqtSignal(list)
    updated_label = QtCore.pyqtSignal(str)
    updated_error = QtCore.pyqtSignal(str)
    request_signal = QtCore.pyqtSignal()
    customer = QtCore.pyqtSignal(str)

    def __init__(self, parent=None):
        super(ConfWorker, self).__init__(parent)
        self.customer.connect(self.getcustomer)

    @QtCore.pyqtSlot()
    def doWork(self):
        self.request_signal.emit()

    @QtCore.pyqtSlot(str)
    def getcustomer(self, text):
        self.configure(text)

    def configure(self, customer_name):
        self.updated_button.emit(["In progress...", False])
        self.updated_label.emit(customer_name)
        time.sleep(5) # During this time you should be able to see color change etc.
        #myaction.myaction(customer_name)# TAKES ~20 SECONDS TO FINISH
        self.updated_button.emit(["Start", True])

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

        # create a QThread and start the thread that handles
        thread = QtCore.QThread(self)
        thread.start()

        # create the worker without a parent so you can move
        self.worker = ConfWorker()
        # the worker moves to another thread
        self.worker.moveToThread(thread)

        self.worker.updated_button.connect(self.updateButton)
        self.worker.updated_label.connect(self.updateLabel)
        self.worker.updated_error.connect(self.updateError)
        self.worker.request_signal.connect(self.sendCustomer)

        self.targetBtn = QtWidgets.QPushButton('Start Configuration', self)
        self.targetBtn.setStyleSheet("QPushButton { background-color: green; color: white }"
                        "QPushButton:disabled { background-color: red; color: white }")
        self.targetBtn.clicked.connect(self.worker.doWork)
        self.targetBtn.setFixedSize(200, 50)

        self.customerlist = QtWidgets.QComboBox(self)
        self.customerlist.addItems(["testcustomer1", "testcustomer2", "testcustomer3"])
        self.customerlist.setFixedSize(200, 50)

        self.label = QtWidgets.QLabel(self, alignment=QtCore.Qt.AlignCenter)
        self.label.setStyleSheet('font-size: 30pt; font-family: Courier; color: green;')
        self.label.setFixedSize(400,50)

        self.error_label = QtWidgets.QLabel(self, alignment=QtCore.Qt.AlignCenter)
        self.error_label.setStyleSheet('font-size: 30pt; font-family: Courier; color: red;')
        self.error_label.setFixedSize(400,50)

        lay = QtWidgets.QVBoxLayout(self)
        lay.addWidget(self.customerlist, alignment=QtCore.Qt.AlignCenter)
        lay.addWidget(self.label, alignment=QtCore.Qt.AlignCenter)
        lay.addWidget(self.error_label, alignment=QtCore.Qt.AlignCenter)
        lay.addWidget(self.targetBtn, alignment=QtCore.Qt.AlignCenter)
        self.setFixedSize(400, 550)

    @QtCore.pyqtSlot()
    def sendCustomer(self):
        self.worker.customer.emit(self.customerlist.currentText())

    @QtCore.pyqtSlot(list)
    def updateButton(self, button_list):
        self.targetBtn.setText(button_list[0])
        self.targetBtn.setEnabled(button_list[1])

    @QtCore.pyqtSlot(str)
    def updateLabel(self, label_text):
        self.label.setText(label_text)

    @QtCore.pyqtSlot(str)
    def updateError(self, error_text):
        self.error_label.setText(error_text)


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

观察:另外,正如您在我的解决方案中看到的,QThread 将是窗口的子窗口,因为 QThread 是处理另一个线程的 QObject

【讨论】:

    【解决方案2】:

    你真的写了非常困难的简单的东西。 试试看:

    import sys
    from PyQt5.QtWidgets import QWidget, QApplication, QPushButton, QComboBox, QLabel
    from PyQt5.QtCore    import QThread, pyqtSignal
    
    class ConfWorker(QThread):
        threadSignal = pyqtSignal(str)
        finishSignal = pyqtSignal(str)
    
        def __init__(self, startParm):
            super().__init__()
            self.startParm = startParm   # Initialize the parameters passed to the task 
    
        def run(self):
            # Do something...
            for i in range(20):
                text = "In progress ................." \
                        if i%2==0 else "In progress {}".format(self.startParm)
                self.threadSignal.emit(text)
                QThread.msleep(500)
            self.finishSignal.emit(self.startParm)
    
    
    class ConfGUI(QWidget):
        def __init__(self):
            super().__init__()
            self.setGeometry(800, 100, 400, 550)
            self.setFixedSize(400, 550)        
    
            self.targetBtn = QPushButton('Start Configuration', self)
            self.targetBtn.setStyleSheet(
                            "QPushButton { background-color: green; color: white;}"
                            "QPushButton:disabled { background-color: red; color: white;}"
                            )
            self.targetBtn.setGeometry(100, 400, 200, 50)
            self.targetBtn.clicked.connect(self.workerStart)           
    
            self.customerlist = QComboBox(self)
            self.customerlist.setGeometry(100, 50, 200, 50)
            self.customerlist.setObjectName("customer")
            self.customerlist.addItem("testcustomer1")
            self.customerlist.addItem("testcustomer2")
            self.customerlist.addItem("testcustomer3")
            self.customerlist.activated[str].connect(self.comboActivated)
            self.startParm = "testcustomer1"
    
            self.label = QLabel(self)
            self.label.setStyleSheet('font-size: 30pt; font-family: Courier; color: green;')
            self.label.setGeometry(70,250,400,50)
    
        def workerStart(self):
            self.targetBtn.setText("In progress...")
            self.targetBtn.setEnabled(False)
            self.label.setText("")
            self.worker = ConfWorker(self.startParm)  
            self.worker.threadSignal.connect(self.on_threadSignal)
            self.worker.finishSignal.connect(self.on_finishSignal)        
            self.worker.start()                     
    
        def on_threadSignal(self, text):
            self.targetBtn.setText(text)
    
        def on_finishSignal(self, text):
            self.targetBtn.setEnabled(True)
            self.targetBtn.setText("Start Configuration'")
            self.label.setText(text)
    
        def comboActivated(self, text):
            self.startParm = text
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        ex  = ConfGUI()
        ex.show()
        sys.exit(app.exec_())
    

    【讨论】:

    • 您的设计与OP要求的不同,您使用的是覆盖QThread的run方法,这是一个简单但有限的设计,例如假设您有n个任务在执行用户需求很大,而且他们不会一个接一个地运行,他们的方法就会失败。有一个更好的设计是创建一个工作在另一个线程中并在该新线程中执行每个任务而不阻塞 GUI。总之,您的设计简单但有限。
    猜你喜欢
    • 2015-10-29
    • 2017-10-05
    • 1970-01-01
    • 2010-10-29
    • 2020-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-09
    相关资源
    最近更新 更多