PyQt5:如何向工作线程发送信号

Posted

技术标签:

【中文标题】PyQt5:如何向工作线程发送信号【英文标题】:PyQt5: How to send a signal to a worker thread 【发布时间】:2016-12-07 19:44:36 【问题描述】:

我知道如何将信号从工作线程发送回主 GUI 线程,但是如何将信号从主线程发送到工作线程?

这是一些包含信号和槽的示例代码。在这里,我将信号发送回主线程,但我该如何反方向呢?

这里的目标是在我希望线程停止时发送一个信号将 self.do 的值更改为 0。

这里是主文件,我会把 UI 文件放在下面

from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
from progressUI import Ui_Form

import sys
import time

class ProgressBar(QObject):
    progress = pyqtSignal(int)
    kill = pyqtSignal()

    def __init__(self, timer, parent=None):
        super(ProgressBar, self).__init__(parent)
        self.time = timer
        self.do = 1

    def work(self):

        while self.do == 1:
            y = 0
            for _ in range(100):
                time.sleep(.1)
                y += 1
                self.progress.emit(y)
            break

        self.kill.emit()

    @pyqtSlot(str)
    def worker_slot(self, sentence):
        print(sentence)

class Go(QMainWindow, Ui_Form, QObject):
    custom_signal = pyqtSignal(str)

    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.setupUi(self)
        self.progressBar.setValue(0)
        self.startThread()

    @pyqtSlot(int)
    def updateProgress(self, val):
        self.progressBar.setValue(val)
        self.custom_signal.emit('hi from main thread')

    def startThread(self):
        self.progressThread = ProgressBar(60)
        self.thread = QThread()
        self.progressThread.moveToThread(self.thread)

        self.progressThread.progress.connect(self.updateProgress)
        self.progressThread.kill.connect(self.thread.quit)
        self.custom_signal.connect(self.progressThread.worker_slot)
        self.thread.started.connect(self.progressThread.work)

        self.thread.start()



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

这是 UI 文件。

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(658, 118)
        self.progressBar = QtWidgets.QProgressBar(Form)
        self.progressBar.setGeometry(QtCore.QRect(30, 40, 601, 23))
        self.progressBar.setProperty("value", 24)
        self.progressBar.setObjectName("progressBar")
        self.label = QtWidgets.QLabel(Form)
        self.label.setGeometry(QtCore.QRect(45, 75, 581, 26))
        self.label.setAlignment(QtCore.Qt.AlignCenter)
        self.label.setObjectName("label")

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "Form"))
        self.label.setText(_translate("Form", "TextLabel"))

【问题讨论】:

您的代码无法运行。存在拼写错误,我们无法访问您的 UI。 不,它没有运行代码。这是一个示例供您查看。 好的,所以我看了一下,我发现......它不起作用。无论如何,是什么阻止您向工作人员发送信号? 请参阅***.com/a/35534047/1994235,但请注意,您不能在工作人员中拥有一个永远循环的函数,因为这会阻止工作人员处理信号。用其他东西替换循环(比如QTimer,但要确保它存在于正确的线程中——那里有一些微妙之处) @squirtgun 我不确定为什么链接不是您要找的。主线程中的信号是带有QPushButtonclicked 信号。随意使用主线程中存在的任何预先存在的信号,或者在位于主线程中的QObject 中定义一个新信号。 slot 是 worker 函数中的一个方法。因此,信号从主线程发出并调用工作函数中的一个槽。这不是你要找的吗?该示例显示了主线程和工作线程之间的双向通信。不确定您还想要什么? 【参考方案1】:

如何向工作线程发送信号?与从工作线程向 GUI 发送信号的方式完全相同。我希望它会更加不同。

@three-pineapples 链接到主线程和工作线程之间双向通信的优秀示例。

如果您想在主 GUI 线程中创建自定义信号,您需要确保继承 QObject,然后才能创建自定义信号。

我在原始帖子中更新了我的代码以包含 UI 文件以便您可以运行它,并且我在 GUI 线程中包含了一个自定义信号的示例,它向工作人员发送信号。

但是,在 for 循环完成之前,您不会看到 print 语句的输出,因为它会阻止 worker 处理信号,正如@three-pineapples 所说的那样。

所以,虽然这不是最好的例子,但希望如果有人在理解这个概念时遇到同样的问题,也许这会有所帮助。

【讨论】:

【参考方案2】:

您好,在另一类项目中遇到了同样的问题,见PyQt5 unable to stop/kill QThread,

在这里找到了非常丰富的解决方案/解释:Stopping an infinite loop in a worker thread in PyQt5 the simplest way 并尝试使用第二个建议的解决方案:解决方案 2:将可变变量作为控制变量传递,选择这个,因为我的项目没有无限循环,但是直到一个目录被清空的 for 循环是 subolders/files。

要了解它是如何工作的,请查看我的结果,应用于上面的代码:

用户界面文件progressUI_2.py

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(658, 218)
        self.progressBar = QtWidgets.QProgressBar(Form)
        self.progressBar.setGeometry(QtCore.QRect(30, 40, 581, 23))
        self.progressBar.setProperty("value", 24)
        self.progressBar.setObjectName("progressBar")
        self.label = QtWidgets.QLabel(Form)
        self.label.setGeometry(QtCore.QRect(45, 75, 581, 26))
        self.label.setAlignment(QtCore.Qt.AlignCenter)
        self.label.setObjectName("label")
        
        self.button = QtWidgets.QPushButton('press here', Form)
        self.button.setGeometry(QtCore.QRect(30, 115, 581, 26))
        
        self.button.setObjectName("button")

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "Form"))
        self.label.setText(_translate("Form", "TextLabel"))

主脚本文件main.py

from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
from progressUI_2 import Ui_Form

import sys
import time

class ProgressBar(QObject):
    progress = pyqtSignal(int)
    kill = pyqtSignal()

    def __init__(self, timer, ctrl, parent=None):
        super(ProgressBar, self).__init__(parent)
        self.time = timer
        self.do = 1
        
        self.flag = 'off'
        
        self.ctrl = ctrl # dict with your control var
        


    def work(self):
        
        print('Entered run in worker thread')
        print('id of ctrl in worker:', id(self.ctrl))
        self.ctrl['break'] = False

        while self.do == 1:
            y = 0
            for _ in range(100):
                time.sleep(.1)
                y += 1
                self.progress.emit(y)
                
                if self.flag == 'on':
                    break
                # print('flag : ', self.flag, 'self.do : ', self.do)
                
                if self.ctrl['break'] == True :
                    break
                
            break

        # self.kill.emit()
        
    @pyqtSlot(str)
    def worker_slot2(self, sentence2):
        self.flag = 'on'
        self.do = 0
        print('from worker !!!!', sentence2, 'self.flag : ', self.flag,'self.do : ', self.do)   
        

    @pyqtSlot(str)
    def worker_slot(self, sentence):
        print(sentence)

class Go(QMainWindow, Ui_Form, QObject):
    custom_signal = pyqtSignal(str)
    button_signal = pyqtSignal(str)

    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.setupUi(self)
        self.progressBar.setValue(0)
        
        
        
        self.button.clicked.connect(self.clicked)
        
        self.ctrl = 'break': False # dict with your control variable
        print('id of ctrl in main:', id(self.ctrl))
        
        self.startThread()
        
    def clicked(self):
        print('clicked')
        self.button_signal.emit('emitted from go ')
        self.flag = 'on'
        self.do = 0
        # self.ctrl['break'] = True

    @pyqtSlot(int)
    def updateProgress(self, val):
        self.progressBar.setValue(val)
        self.custom_signal.emit('hi from main thread')

    def startThread(self):
        self.progressThread = ProgressBar(60, self.ctrl)
        self.thread = QThread()
        self.progressThread.moveToThread(self.thread)

        self.progressThread.progress.connect(self.updateProgress)
        self.progressThread.kill.connect(self.thread.quit)
        self.custom_signal.connect(self.progressThread.worker_slot)
        
        self.thread.started.connect(self.progressThread.work)
        
        self.button_signal.connect(self.progressThread.worker_slot2)
        
        
        self.thread.start()



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

尝试运行main.py

注释掉和取消注释self.ctrl['break'] = True

在:

 def clicked(self):
        print('clicked')
        self.button_signal.emit('emitted from go ')
        self.flag = 'on'
        self.do = 0
        # self.ctrl['break'] = True

看看你是否能够停止按下 QBButton 的进度条,

请注意如何在代码的各个部分更改 self.doself.flag 被证明是不成功的,不确定是否只是因为我应该将它们传递给工作人员

【讨论】:

以上是关于PyQt5:如何向工作线程发送信号的主要内容,如果未能解决你的问题,请参考以下文章

从主函数向线程发送信号?

有没有办法从另一个进程向线程发送信号?

如何多次发送 SIGINT 信号

PyQt5 - 如何从工作线程发出信号以通过 GUI 线程调用事件

pthread中向线程发送信号

如何向正在运行的詹金斯作业发送信号