问题
I am trying to make a simple communication between a worker thread, in this case it is called WorkToDo
via the PyQt5 Signals and Slots mechanism. I can reliably send data from the Worker to the Gui thread via this mechanism, but I cannot do the same to the gui thread. From my research I have found that this is due to the fact that I have overridden the run
function with my own logic. My question, is there any way to manually handle the execution of signals in the worker thread? Is there a better way to accomplish this?
EDIT:
I am actually not overriding run
as I do not see run
listed in the documentation for QObject. I am mistaken as this is a default function of a QThread object. This is more perplexing to me. Unless I am just further misunderstanding this.
import sys
import time
import qdarkstyle
from PyQt5.QtWidgets import (
QWidget, QLineEdit, QMessageBox, QPushButton, QVBoxLayout, QHBoxLayout, QTabWidget,
QComboBox, QProgressBar, QApplication, QLabel, QGroupBox, QFileDialog, QTextEdit,
QStyleFactory, QFormLayout
)
from PyQt5 import (Qt, QtCore)
from PyQt5.QtCore import *
class Command:
def __init__(self, **kwargs):
self.cmd = kwargs.get('cmd', None)
self.args = kwargs.get('args')
class CommandSignals(QObject):
command = pyqtSignal(Command)
class WorkToDo(QObject):
def __init__(self):
super().__init__()
self.counter = 0
self.signals = CommandSignals()
@QtCore.pyqtSlot(Command)
def process_command(self, command):
"""
@brief process update from gui thread
@param self self
@param command command
@return none
"""
print(f'Update from GUI: {command.__dict__}')
if command.cmd == 'add_to_counter':
self.counter = self.counter + command.args.get('addition', 0)
@pyqtSlot()
def run(self):
"""
@brief actual thread function to run,
started via thread.started signal (why its decorated with a pyqtSlot)
@param self obj ref
@return none
"""
while True:
QThread.sleep(1)
# emit text change signal to main gui thread
cmd = Command(
cmd = 'update_text',
args = {
'text': f'Hello update {self.counter}'
}
)
print(f'Update from worker: {cmd.__dict__}')
self.signals.command.emit(cmd)
self.counter += 1
class Gui(QWidget):
def __init__(self):
super().__init__()
""" make gui elements """
layout = QFormLayout()
self.text_line = QLineEdit()
self.add_button = QPushButton('Add 10 To Counter')
layout.addRow(self.text_line)
layout.addRow(self.add_button)
self.add_button.clicked.connect(self.issue_button_update)
self.signals = CommandSignals()
self.MakeThreadWorker()
""" finalize gui """
self.setLayout(layout)
self.setWindowTitle('Sync Thread Command/Response test')
self.show()
def MakeThreadWorker(self):
self.worker_thread = QThread()
self.worker = WorkToDo()
""" incoming gui update command, works """
self.worker.signals.command.connect(self.process_command)
""" outgoing update to worker thread, does not work """
self.signals.command.connect(self.worker.process_command)
""" signal to start the thread function, works """
self.worker_thread.started.connect(self.worker.run)
self.worker_thread.start()
self.worker.moveToThread(self.worker_thread)
@pyqtSlot(Command)
def process_command(self, command):
"""
@brief process update from work thread
@param self self
@param command command object
@return none
"""
if command.cmd == 'update_text':
text_update = command.args.get('text', '')
self.text_line.setText(text_update)
def issue_button_update(self):
cmd = Command(
cmd = 'add_to_counter',
args = {
'addition': 10
}
)
print('Button Clicked!')
self.signals.command.emit(cmd)
if __name__ == '__main__':
APPLICATION = QApplication([])
APPLICATION.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
gui = Gui()
sys.exit(APPLICATION.exec_())
回答1:
The problem is that with True while you are blocking the event loop of the secondary thread preventing the signals from being received, if you want to do a periodic task then use a QTimer.
import sys
import time
import qdarkstyle
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QThread, QTimer
from PyQt5.QtWidgets import QApplication, QFormLayout, QLineEdit, QPushButton, QWidget
class Command:
def __init__(self, **kwargs):
self.cmd = kwargs.get("cmd", None)
self.args = kwargs.get("args")
class CommandSignals(QObject):
command = pyqtSignal(Command)
class WorkToDo(QObject):
def __init__(self):
super().__init__()
self.counter = 0
self.signals = CommandSignals()
self.timer = QTimer(self, timeout=self.run, interval=1000)
@pyqtSlot()
def start(self):
self.timer.start()
@pyqtSlot()
def stop(self):
self.timer.stop()
@pyqtSlot(Command)
def process_command(self, command):
"""
@brief process update from gui thread
@param self self
@param command command
@return none
"""
print(f"Update from GUI: {command.__dict__}")
if command.cmd == "add_to_counter":
self.counter = self.counter + command.args.get("addition", 0)
@pyqtSlot()
def run(self):
print(self.thread(), self.timer.thread())
cmd = Command(cmd="update_text", args={"text": f"Hello update {self.counter}"})
print(f"Update from worker: {cmd.__dict__}")
self.signals.command.emit(cmd)
self.counter += 1
class Gui(QWidget):
def __init__(self):
super().__init__()
""" make gui elements """
layout = QFormLayout()
self.text_line = QLineEdit()
self.add_button = QPushButton("Add 10 To Counter")
layout.addRow(self.text_line)
layout.addRow(self.add_button)
self.add_button.clicked.connect(self.issue_button_update)
self.signals = CommandSignals()
self.MakeThreadWorker()
""" finalize gui """
self.setLayout(layout)
self.setWindowTitle("Sync Thread Command/Response test")
self.show()
def MakeThreadWorker(self):
self.worker_thread = QThread()
self.worker = WorkToDo()
""" incoming gui update command, works """
self.worker.signals.command.connect(self.process_command)
""" outgoing update to worker thread, does not work """
self.signals.command.connect(self.worker.process_command)
""" signal to start the thread function, works """
self.worker_thread.started.connect(self.worker.start)
self.worker_thread.start()
self.worker.moveToThread(self.worker_thread)
@pyqtSlot(Command)
def process_command(self, command):
"""
@brief process update from work thread
@param self self
@param command command object
@return none
"""
if command.cmd == "update_text":
text_update = command.args.get("text", "")
self.text_line.setText(text_update)
def issue_button_update(self):
cmd = Command(cmd="add_to_counter", args={"addition": 10})
print("Button Clicked!")
self.signals.command.emit(cmd)
if __name__ == "__main__":
APPLICATION = QApplication([])
APPLICATION.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
gui = Gui()
sys.exit(APPLICATION.exec_())
来源:https://stackoverflow.com/questions/59723437/pyqt5-gui-thread-to-worker-thread-signal-slot