如何随着时间将多个复杂信号发送到同一线程?

问题描述 投票:0回答:2

我正在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_())
python multithreading pyqt5 parameter-passing qt-signals
2个回答
0
投票

以下代码成功创建了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_())

0
投票

我认为您可以随时在线程之间发送信号。您得到什么样的错误?这是一个例子。

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_())
© www.soinside.com 2019 - 2024. All rights reserved.