我正在Spyder3.8上使用PyQT5.10编写GUI。用户输入几个参数,包括字符串,整数和浮点数,然后按“发送参数/启动线程”按钮。这将启动一个新线程,在该线程中存储参数,然后函数进行计算。该线程应该立即从GUI中获取这些参数,但是除此之外,GUI还具有可以编辑参数或向线程发送新参数的按钮,希望可以实时更新它们。我的问题:是否可以在不同时间使用信号/插槽将所有这些int / str / float参数发送到同一线程?是否有特定的最佳策略?我还没有在网上找到类似的例子,但是我相信这可能会在许多领域为Python用户广泛应用。我在下面包含了脚本的缩小版本,该脚本制作了GUI,并且能够启动新线程,但无法将初始变量列表传递给正在运行的线程。我尝试了不同的策略,但到目前为止没有任何效果。当前的脚本没有设置用于实时编辑线程变量的功能按钮,因为这会产生错误。我想讨论将变量传递到运行线程的细节,但是我的主要问题仍然是“这可能吗?”
import sys
from PyQt5.QtCore import QThreadPool, QThread, QObject, pyqtSignal, pyqtSlot
from PyQt5.QtCore import *
import time
from PyQt5.QtWidgets import (QApplication, QComboBox, QDialog,
QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit, QPushButton,
QSpinBox, QStyleFactory, QTextEdit, QVBoxLayout)
from PyQt5.QtWidgets import *
import logging
import logging.handlers
# threadpool = QThreadPool()
# print("Multithreading with maximum %d threads" % threadpool.maxThreadCount())
thread_id = int(QThread.currentThreadId())
print('Intro thread ID: ',thread_id)
def trap_exc_during_debug(*args):
print('debug arg:',args)
sys.excepthook = trap_exc_during_debug
# This class sets up the thread
class Worker(QObject):
print('begin ThreadClass')
sig_msg = pyqtSignal(str)
@pyqtSlot(float,float,int,str)
def __init__(self,param1,param2,param3,param4,parent = None):
#print('params in Worker init: ',param1,param2,param3,param4)
self.p1=param1; self.p2=param2; self.p3=param3; self.p4=param4
print('params in Worker init: ',self.p1,self.p2,self.p3,self.p4)
QThread.__init__(self, parent)
thread_id = int(QThread.currentThreadId())
print('Worker __Init__ thread ID: ',thread_id)
self.__id = 1
self.__abort = False
self.__reset = False
print('end Worker init')
@pyqtSlot()
def work(self): # This function is what will happen in the thread when it starts
thread_id = int(QThread.currentThreadId())
print('work thread ID: ',thread_id)
print('params in work fcn: ',self.p1,self.p2,self.p3,self.p4)
p1=self.p1
p2=self.p2
p3=self.p3
p4=self.p4
print('params in work fcn: ',p1,p2,p3,p4)
thread_id = int(QThread.currentThreadId())
print('work thread ID: ',thread_id)
# app.processEvents() # This approach generated a TypeError:
# if fn==1: #("work() missing 1 required positional argument: 'fn'
# p1-=1
# print('New param1 value in thread: ',p1)
# thread_id = int(QThread.currentThreadId())
# print('fcn1b thread ID: ',thread_id)
# elif fn==2:
# p2+=1
# print('New param2 value in thread: ',p2)
# thread_id = int(QThread.currentThreadId())
# print('fcn2b thread ID: ',thread_id)
# elif fn==3:
# p3+=1
# print('New param3 value in thread: ',p3)
# thread_id = int(QThread.currentThreadId())
# print('fcn1b thread ID: ',thread_id)
# elif fn==4:
# p4=param4
# print('New param4 value in thread: ',p4)
# thread_id = int(QThread.currentThreadId())
# print('fcn1b thread ID: ',thread_id)
@pyqtSlot(float)
def fcn1b(self,prm1):
print('New fcn1 value in thread: ',prm1)
thread_id = int(QThread.currentThreadId())
print('fcn1b thread ID: ',thread_id)
@pyqtSlot(float)
def fcn2b(self,prm2):
print('New fcn2 value in thread: ',prm2)
thread_id = int(QThread.currentThreadId())
print('fcn2b thread ID: ',thread_id)
@pyqtSlot(int)
def fcn3b(self,prm3):
print('New fcn3 value in thread: ',prm3)
thread_id = int(QThread.currentThreadId())
print('fcn3b thread ID: ',thread_id)
@pyqtSlot(str)
def fcn4b(self,prm4):
print('New fcn4 value in thread: ',prm4)
thread_id = int(QThread.currentThreadId())
print('fcn4b thread ID: ',thread_id)
#This class creates the GUI, sets up the buttons, starts new thread, allows parameter edits after thread start.
#Parameter edits are seen in GUI and are supposed to be sent to thread also.
class WidgetGallery(QDialog):
sendinitialparams = pyqtSignal(float,float,int,str)
sendparameter1 = pyqtSignal(float)
sendparameter2 = pyqtSignal(float)
sendparameter3 = pyqtSignal(int)
sendparameter4 = pyqtSignal(str)
NUM_THREADS = 1
def __init__(self):
super(WidgetGallery, self).__init__()
self.originalPalette = QApplication.palette()
styleComboBox = QComboBox()
styleComboBox.addItems(QStyleFactory.keys())
self.createTopLeftGroupBox()
self.createTopRightGroupBox()
self.createbottomRightGroupBox()
topLayout = QHBoxLayout()
mainLayout = QGridLayout()
mainLayout.addLayout(topLayout, 0, 0, 1, 2)
mainLayout.addWidget(self.topLeftGroupBox, 2, 0)
mainLayout.addWidget(self.topRightGroupBox, 2, 1)
mainLayout.addWidget(self.bottomRightGroupBox, 3, 1)
mainLayout.setRowStretch(1, 1)
mainLayout.setRowStretch(2, 1)
mainLayout.setColumnStretch(0, 1)
mainLayout.setColumnStretch(1, 1)
self.setLayout(mainLayout)
self.setWindowTitle("GUI")
def createTopLeftGroupBox(self):
self.topLeftGroupBox = QGroupBox("Settings")
l1= QLabel("Param1 Value:")
self.DoubleSpinBox1 = QDoubleSpinBox()
self.DoubleSpinBox1.setMinimum(0)
self.DoubleSpinBox1.setMaximum(99)
self.DoubleSpinBox1.setValue(20.5)
l2 = QLabel("Param2 Value:")
self.DoubleSpinBox2 = QDoubleSpinBox()
self.DoubleSpinBox2.setMinimum(0)
self.DoubleSpinBox2.setMaximum(99)
self.DoubleSpinBox2.setValue(10.5)
l3= QLabel("Param3 Value:")
self.SpinBox1 = QSpinBox()
self.SpinBox1.setMinimum(0)
self.SpinBox1.setMaximum(99)
self.SpinBox1.setValue(5)
l4 = QLabel("Param4 Value:")
self.textEdit1 = QLineEdit('string1')
layout = QGridLayout()
layout.addWidget(l1, 1,0,1,2)
layout.addWidget(self.DoubleSpinBox1, 1, 2, 1, 2)
layout.addWidget(l2, 2,0,1,2)
layout.addWidget(self.DoubleSpinBox2, 2, 2, 1, 2)
layout.addWidget(l3, 3,0,1,2)
layout.addWidget(self.SpinBox1, 3, 2, 1, 2)
layout.addWidget(l4, 4,0,1,2)
layout.addWidget(self.textEdit1, 4, 2, 1, 2)
layout.setRowStretch(5, 1)
self.topLeftGroupBox.setLayout(layout)
def createTopRightGroupBox(self):
self.topRightGroupBox = QGroupBox("Adjust")
self.defaultPushButton0 = QPushButton("Send params/start thread")
self.defaultPushButton0.setDefault(False)
self.defaultPushButton0.pressed.connect(self.start_threads)
self.defaultPushButton1 = QPushButton("Decrease Param1 in GUI/thread", self) # self? # log
self.defaultPushButton1.setDefault(False)
self.defaultPushButton1.pressed.connect(self.func1)
self.defaultPushButton2 = QPushButton("Increase Param2 in GUI/thread")
self.defaultPushButton2.setDefault(False)
self.defaultPushButton2.pressed.connect(self.func2)
self.defaultPushButton3 = QPushButton("Increase Param3 in GUI/thread")
self.defaultPushButton3.setDefault(False)
self.defaultPushButton3.pressed.connect(self.func3)
self.defaultPushButton4 = QPushButton("Send new Param4 to GUI/thread")
self.defaultPushButton4.setDefault(False)
self.defaultPushButton4.pressed.connect(self.func4)
self.defaultPushButton5 = QPushButton("Abort Worker")
self.defaultPushButton5.setDefault(False)
self.defaultPushButton5.pressed.connect(self.abort_workers)
layout = QVBoxLayout()
layout.addWidget(self.defaultPushButton0)
layout.addWidget(self.defaultPushButton1)
layout.addWidget(self.defaultPushButton2)
layout.addWidget(self.defaultPushButton3)
layout.addWidget(self.defaultPushButton4)
layout.addWidget(self.defaultPushButton5)
layout.addStretch(1)
self.topRightGroupBox.setLayout(layout)
def createbottomRightGroupBox(self):
self.bottomRightGroupBox = QGroupBox("Log")
self.log = QTextEdit()
layout = QGridLayout()
layout.addWidget(self.log)
self.bottomRightGroupBox.setLayout(layout)
def func1(self):
param1=float(self.DoubleSpinBox1.value())
param1-=1
self.DoubleSpinBox1.setValue(param1)
self.sendparameter1.emit(param1)
def func2(self):
param2 = float(self.DoubleSpinBox2.value())
param2+=1
self.DoubleSpinBox2.setValue(param2)
self.sendparameter2.emit(param2)
def func3(self):
param3 = int(self.SpinBox1.value())
param3+=1
self.SpinBox1.setValue(param3)
self.sendparameter3.emit(param3)
def func4(self):
param4=str(self.textEdit1.text())
self.sendparameter4.emit(param4)
def start_threads(self):
self.log.append('starting {} thread'.format(self.NUM_THREADS))
self.__workers_done = 0
self.__threads = []
worker = Worker(float,float,int,str)
print('a')
thread = QThread()
thread.setObjectName('thread_1')
print('b')
self.__threads.append((thread, worker))
worker.moveToThread(thread)
# get progress messages from worker:
worker.sig_msg.connect(self.log.append)
# get ready to start worker:
receiving_class = Worker(float,float,int,str)
print('c')
thread.started.connect(worker.work)
#thread.start()
print('d')
param1=float(self.DoubleSpinBox1.value())
param2 = float(self.DoubleSpinBox2.value())
param3 = int(self.SpinBox1.value())
param4 = str(self.textEdit1.text())
#time.sleep(.0001) # .01 # .005 # .001
print('Transfer data to Worker init')
self.sendinitialparams.emit(param1,param2,param3,param4)
thread.start()
print('End of start_threads')
@pyqtSlot()
def abort_workers(self):
for thread, worker in self.__threads:
print(thread)
thread.quit()
thread.wait()
self.log.append('Thread exited')
print('Thread quit')
# This class coordinates many of the above classes and functions.
if __name__ == "__main__":
print('1')
app = QApplication([])
WG = WidgetGallery()
WG.show()
print('2')
receiving_class = Worker(float,float,int,str)
print('3')
WG.sendinitialparams.connect(receiving_class.__init__) # work # __init__
WG.sendparameter1.connect(receiving_class.fcn1b)
WG.sendparameter2.connect(receiving_class.fcn2b)
WG.sendparameter3.connect(receiving_class.fcn3b)
WG.sendparameter4.connect(receiving_class.fcn4b)
print('4')
sys.exit(app.exec_())
以下代码成功创建了GUI和线程,但是变量没有传递给线程。请点击“开始线程”,然后在“ GUI /线程”中点击“减少参数1”。
import sys; from PyQt5.QtCore import QThreadPool, QThread, QObject,QRunnable
from PyQt5.QtCore import *; from PyQt5.QtWidgets import *
from PyQt5.QtWidgets import (QApplication, QComboBox, QDialog, QGridLayout, QGroupBox, QLabel,
QHBoxLayout, QLineEdit, QPushButton, QSpinBox, QStyleFactory, QTextEdit, QVBoxLayout)
import logging; import logging.handlers; from functools import partial; from PyQt5.Qt import *
thread_id = int(QThread.currentThreadId())
print('Intro thread ID: ',thread_id)
class Worker(QRunnable):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class Signals(QObject):
sendinitialparams = pyqtSignal(float)
sendparameter1 = pyqtSignal(float)
self.signals = Signals()
self.signals.sendinitialparams.connect(partial(setattr, self, 'sendinitialparams'))
self.signals.sendparameter1.connect(partial(setattr, self, 'sendparameter1'))
print('args in thread: ',*args)
for b in args:
print('arg: ',b)
def run(self,*args,**kwargs):
print('begin work')
thread_id = int(QThread.currentThreadId())
print('work thread ID: ',thread_id)
class WidgetGallery(QDialog):
def __init__(self,*args,**kwargs):
super(WidgetGallery, self).__init__(*args,**kwargs)
self.originalPalette = QApplication.palette()
styleComboBox = QComboBox()
styleComboBox.addItems(QStyleFactory.keys())
self.createTopLeftGroupBox()
self.createTopRightGroupBox()
self.createbottomRightGroupBox()
topLayout = QHBoxLayout()
mainLayout = QGridLayout()
mainLayout.addLayout(topLayout, 0, 0, 1, 2)
mainLayout.addWidget(self.topLeftGroupBox, 2, 0)
mainLayout.addWidget(self.topRightGroupBox, 2, 1)
mainLayout.addWidget(self.bottomRightGroupBox, 3, 1)
mainLayout.setRowStretch(1, 1)
mainLayout.setRowStretch(2, 1)
mainLayout.setColumnStretch(0, 1)
mainLayout.setColumnStretch(1, 1)
self.setLayout(mainLayout)
self.setWindowTitle("GUI")
def createTopLeftGroupBox(self):
self.topLeftGroupBox = QGroupBox("Settings")
l1= QLabel("Param1 Value:")
self.DoubleSpinBox1 = QDoubleSpinBox()
self.DoubleSpinBox1.setMinimum(0)
self.DoubleSpinBox1.setMaximum(99)
self.DoubleSpinBox1.setValue(20.5)
layout = QGridLayout()
layout.addWidget(l1, 1,0,1,2)
layout.addWidget(self.DoubleSpinBox1, 1, 2, 1, 2)
layout.setRowStretch(5, 1)
self.topLeftGroupBox.setLayout(layout)
def createTopRightGroupBox(self):
self.topRightGroupBox = QGroupBox("Adjust")
self.defaultPushButton0 = QPushButton("Start thread")
self.defaultPushButton0.setDefault(False)
self.defaultPushButton0.pressed.connect(self.start_threads)
self.defaultPushButton1 = QPushButton("Decrease Param1 in GUI/thread", self)
self.defaultPushButton1.setDefault(False)
self.defaultPushButton1.pressed.connect(self.func1)
layout = QVBoxLayout()
layout.addWidget(self.defaultPushButton0)
layout.addWidget(self.defaultPushButton1)
layout.addStretch(1)
self.topRightGroupBox.setLayout(layout)
def createbottomRightGroupBox(self):
self.bottomRightGroupBox = QGroupBox("Log")
self.log = QTextEdit()
layout = QGridLayout()
layout.addWidget(self.log)
self.bottomRightGroupBox.setLayout(layout)
def func1(self):
param1=float(self.DoubleSpinBox1.value())
param1-=1
self.DoubleSpinBox1.setValue(param1)
#self.param1=param1
#WG=WidgetGallery()
self.worker=Worker()
self.worker.signals.sendparameter1.emit(self.DoubleSpinBox1.value())
def start_threads(self):
self.worker = Worker()
thread = QThreadPool.globalInstance()
thread.start(self.worker)
self.send_signals()
print('End of start_threads')
def send_signals(self):
self.worker2=Worker()
self.worker2.signals.sendparameter1.emit(self.DoubleSpinBox1.value())
if __name__ == "__main__":
app = QApplication(sys.argv)
WG = WidgetGallery()
WG.show()
sys.exit(app.exec_())
我认为您可以随时在线程之间发送信号。您得到什么样的错误?这是一个例子。
import time
from functools import partial
from PyQt5.Qt import *
import sys
class WorkerThread(QRunnable):
def __init__(self):
super().__init__()
self.count = self.base_int = 0
self.stop_signal = False
self.progress = 0
class Signals(QObject):
count = pyqtSignal(int)
base_int = pyqtSignal(int)
stop_signal = pyqtSignal(bool)
self.signals = Signals()
self.signals.count.connect(partial(setattr, self, 'count'))
self.signals.base_int.connect(partial(setattr, self, 'base_int'))
self.signals.stop_signal.connect(partial(setattr, self, 'stop_signal'))
def run(self):
while not self.stop_signal:
print('worker - count:', self.count)
self.progress += self.base_int
print('worker - working hard:', self.progress)
time.sleep(1)
print('worker - end')
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("My Awesome App")
widget = QWidget()
central_layout = QVBoxLayout(widget)
self.setCentralWidget(widget)
self.count = 0
self.base_int_input = QSpinBox()
central_layout.addWidget(self.base_int_input)
self.send_signals_button = QPushButton('send_signals')
self.send_signals_button.clicked.connect(self.send_signals)
self.stop_button = QPushButton('stop')
central_layout.addWidget(self.send_signals_button)
central_layout.addWidget(self.stop_button)
self.setGeometry(300, 300, 250, 180)
self.worker_thread = WorkerThread()
thread_pool = QThreadPool.globalInstance()
thread_pool.start(self.worker_thread)
self.stop_button.clicked.connect(partial(
self.worker_thread.signals.stop_signal.emit, True))
def send_signals(self):
self.worker_thread.signals.count.emit(self.count)
self.worker_thread.signals.base_int.emit(
self.base_int_input.value())
self.count += 1
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
但是我不认为这是一个好主意,因为python GIL会使工作量大。为什么不用纯python做呢?只需生成一个python进程即可进行计算。
编辑您的示例:
import sys; from PyQt5.QtCore import QThreadPool, QThread, QObject,QRunnable
from PyQt5.QtCore import *; from PyQt5.QtWidgets import *
from PyQt5.QtWidgets import (QApplication, QComboBox, QDialog, QGridLayout, QGroupBox, QLabel,
QHBoxLayout, QLineEdit, QPushButton, QSpinBox, QStyleFactory, QTextEdit, QVBoxLayout)
import logging; import logging.handlers; from functools import partial; from PyQt5.Qt import *
thread_id = int(QThread.currentThreadId())
print('Intro thread ID: ',thread_id)
from queue import Queue, Empty as EmptyQueue
class Worker(QRunnable):
def __init__(self):
super().__init__()
self.parameter_queue = Queue()
class Signals(QObject):
sendinitialparams = pyqtSignal(float)
sendparameter1 = pyqtSignal(float)
self.signals = Signals()
self.signals.sendinitialparams.connect(self.parameter_queue.put)
self.signals.sendparameter1.connect(self.parameter_queue.put)
def run(self):
thread_id = int(QThread.currentThreadId())
me = 'Worker ID-%d:' % thread_id
print(me, 'begin work')
while True:
try:
parameter = self.parameter_queue.get(timeout=10)
except EmptyQueue:
break
print(me, 'input -', parameter)
output = self.work_hard(parameter)
print(me, 'output -', output)
print(me, 'end work, goodbye')
def work_hard(self, input):
output = input
return output
class WidgetGallery(QDialog):
def __init__(self,*args,**kwargs):
super(WidgetGallery, self).__init__(*args,**kwargs)
self.originalPalette = QApplication.palette()
styleComboBox = QComboBox()
styleComboBox.addItems(QStyleFactory.keys())
self.createTopLeftGroupBox()
self.createTopRightGroupBox()
self.createbottomRightGroupBox()
topLayout = QHBoxLayout()
mainLayout = QGridLayout()
mainLayout.addLayout(topLayout, 0, 0, 1, 2)
mainLayout.addWidget(self.topLeftGroupBox, 2, 0)
mainLayout.addWidget(self.topRightGroupBox, 2, 1)
mainLayout.addWidget(self.bottomRightGroupBox, 3, 1)
mainLayout.setRowStretch(1, 1)
mainLayout.setRowStretch(2, 1)
mainLayout.setColumnStretch(0, 1)
mainLayout.setColumnStretch(1, 1)
self.setLayout(mainLayout)
self.setWindowTitle("GUI")
def createTopLeftGroupBox(self):
self.topLeftGroupBox = QGroupBox("Settings")
l1= QLabel("Param1 Value:")
self.DoubleSpinBox1 = QDoubleSpinBox()
self.DoubleSpinBox1.setMinimum(0)
self.DoubleSpinBox1.setMaximum(99)
self.DoubleSpinBox1.setValue(20.5)
layout = QGridLayout()
layout.addWidget(l1, 1,0,1,2)
layout.addWidget(self.DoubleSpinBox1, 1, 2, 1, 2)
layout.setRowStretch(5, 1)
self.topLeftGroupBox.setLayout(layout)
def createTopRightGroupBox(self):
self.topRightGroupBox = QGroupBox("Adjust")
self.defaultPushButton0 = QPushButton("Start thread")
self.defaultPushButton0.setDefault(False)
self.defaultPushButton0.pressed.connect(self.start_threads)
self.defaultPushButton1 = QPushButton("Decrease Param1 in GUI/thread", self)
self.defaultPushButton1.setDefault(False)
self.defaultPushButton1.pressed.connect(self.func1)
layout = QVBoxLayout()
layout.addWidget(self.defaultPushButton0)
layout.addWidget(self.defaultPushButton1)
layout.addStretch(1)
self.topRightGroupBox.setLayout(layout)
def createbottomRightGroupBox(self):
self.bottomRightGroupBox = QGroupBox("Log")
self.log = QTextEdit()
layout = QGridLayout()
layout.addWidget(self.log)
self.bottomRightGroupBox.setLayout(layout)
def func1(self):
param1=float(self.DoubleSpinBox1.value())
param1-=1
self.DoubleSpinBox1.setValue(param1)
#self.param1=param1
#WG=WidgetGallery()
# self.worker=Worker() <--- send to the same thread?
print('self.DoubleSpinBox1.value()', self.DoubleSpinBox1.value())
self.worker.signals.sendparameter1.emit(self.DoubleSpinBox1.value())
def start_threads(self):
self.worker = Worker()
thread_pool = QThreadPool.globalInstance()
thread_pool.start(self.worker)
self.send_signals()
print('End of start_threads')
def send_signals(self):
self.worker2=Worker() # not started....so, no effect
self.worker2.signals.sendparameter1.emit(self.DoubleSpinBox1.value())
if __name__ == "__main__":
app = QApplication(sys.argv)
WG = WidgetGallery()
WG.show()
sys.exit(app.exec_())