工作线程和主线程 window 之间的 PyQt5 信号通信工作不正常
PyQt5 signal communication between worker thread and main window is not working correctly
我正在使用 PyQt5 创建一个应用程序,它具有非常简单的用户界面。有一个可以从中选择值的下拉列表,然后有一个可以单击的按钮,将根据下拉列表中的当前值执行操作。
目前,如果我 运行 下面的代码,我可以从下拉列表中获取一个值,它会在 myaction.myaction(customer_name)
为 运行 之前发送到工作线程。代码也 运行s 很好,但是当工作线程中的函数 运行s 时,GUI 没有按照我希望的方式工作。当我单击 start 按钮时,它应该向 GUI 发出信号以禁用该按钮,更改其标签和颜色,但这种情况从未发生过。当函数完成后,它应该变回原来的形式。
是我处理信号的方式有问题,还是我的 类 中有冗余功能?每次单击按钮时将该下拉列表值发送到工作线程的正确方法是什么,这样我就可以简单地将它用作那里的变量?
我在网上找到的每一个可能的解决方案都让我很兴奋,但是 none 中的一些已经为我工作,或者其中一些太混乱了我无法理解。
这是我已经完成的一些答案
Sending a signal from main function to a thread?
#!/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_())
你真的写出非常难的简单东西。
试一试:
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_())
这个问题是一个很普遍的错误观念造成的,他们认为QThread是一个Qt Thread,也就是Qt新建的线程,但是不是,QThread
是线程 handler,如 the docs:
所示
The QThread class provides a platform-independent way to manage
threads.
A QThread object manages one thread of control within the program.
QThreads begin executing in run(). By default, run() starts the event
loop by calling exec() and runs a Qt event loop inside the thread.
另一个线程上 运行 的唯一部分是 运行 方法,在你的情况下你没有调用,因为你的逻辑不同,你不想连续执行一个沉重的任务,但应用户要求,因此设计必须是工人的设计,但您使用 QThread 作为基础 class,这是不正确的,您必须使用 QObject 作为基础 class 并移动它到一个新线程,以便 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
将是 window 的子代,因为 QThread
是处理另一个线程的 QObject
。
我正在使用 PyQt5 创建一个应用程序,它具有非常简单的用户界面。有一个可以从中选择值的下拉列表,然后有一个可以单击的按钮,将根据下拉列表中的当前值执行操作。
目前,如果我 运行 下面的代码,我可以从下拉列表中获取一个值,它会在 myaction.myaction(customer_name)
为 运行 之前发送到工作线程。代码也 运行s 很好,但是当工作线程中的函数 运行s 时,GUI 没有按照我希望的方式工作。当我单击 start 按钮时,它应该向 GUI 发出信号以禁用该按钮,更改其标签和颜色,但这种情况从未发生过。当函数完成后,它应该变回原来的形式。
是我处理信号的方式有问题,还是我的 类 中有冗余功能?每次单击按钮时将该下拉列表值发送到工作线程的正确方法是什么,这样我就可以简单地将它用作那里的变量?
我在网上找到的每一个可能的解决方案都让我很兴奋,但是 none 中的一些已经为我工作,或者其中一些太混乱了我无法理解。
这是我已经完成的一些答案
Sending a signal from main function to a thread?
#!/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_())
你真的写出非常难的简单东西。 试一试:
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_())
这个问题是一个很普遍的错误观念造成的,他们认为QThread是一个Qt Thread,也就是Qt新建的线程,但是不是,QThread
是线程 handler,如 the docs:
The QThread class provides a platform-independent way to manage threads.
A QThread object manages one thread of control within the program. QThreads begin executing in run(). By default, run() starts the event loop by calling exec() and runs a Qt event loop inside the thread.
另一个线程上 运行 的唯一部分是 运行 方法,在你的情况下你没有调用,因为你的逻辑不同,你不想连续执行一个沉重的任务,但应用户要求,因此设计必须是工人的设计,但您使用 QThread 作为基础 class,这是不正确的,您必须使用 QObject 作为基础 class 并移动它到一个新线程,以便 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
将是 window 的子代,因为 QThread
是处理另一个线程的 QObject
。