简体   繁体   中英

PyQt5: How to send a signal to a worker thread

I know how to send signals from worker threads back to the main GUI thread, but how can I send signals from the main thread to the worker thread?

Here's some sample code which includes a signal and slot. Here I'm sending signals back to the main thread, but how can I go in the opposite direction?

The goal here being to send a signal to change the value of self.do to 0 when I want the thread to stop.

Here's the main file and I'll put the UI file below

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_())

Here's the UI file.

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"))

How do you send a signal to a worker thread? Exactly the same way as sending a signal from a worker thread to the GUI. I expected it to be more different.

@three-pineapples linked to an excellent example of bi-directional communication between the main thread and a worker thread.

If you want to create a custom signal in the main GUI thread, you need to make sure you inherit QObject and then you'll be able to create custom signals.

I updated my code in the original post to include the UI file so you can run it, and I included an example of a custom signal in the GUI thread that sends a signal to the worker.

However you will not see the output of the print statement until the for loop has finished as it blocks the worker from processing signals, as @three-pineapples also stated.

So, although it's not the best example, hopefully if someone is having the same trouble understanding the concept, maybe this will help.

Hi encountered the same proble on another type of project, see PyQt5 unable to stop/kill QThread ,

found very informative solution/explanation here: Stopping an infinite loop in a worker thread in PyQt5 the simplest way and tried to use the second proposed solution: Solution 2: Passing a mutable as a control variable , choose this one because my project doesnt have an infinite loop, but a for loop running until a directory is emptied of all is subolders/files.

To try to understand how it works see my result, applied to code above:

UI file 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 script file 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_())

try to run the main.py

commenting out and uncommenting self.ctrl['break'] = True

in:

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

and see if you are able to stop the progress bar pushing the QBButton,

notice how trying to change self.do and self.flag in various part of the code prove to be unsuccessful, not sure if it just because I should have passed them to the worker

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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