【问题标题】:Python PyQt4 Threading, sharing variablesPython PyQt4 线程,共享变量
【发布时间】:2017-09-10 07:06:55
【问题描述】:

过去几天我一直在研究这个主题,但没有找到适合我的应用程序的答案。我正在使用 PyQt4 在 python 2.7 中构建一个 GUI 来与机器人交互。我需要一个线程(主线程)来运行使用 QT Designer 创建的 GUI,另一个线程(工作线程)来不断地与机器人之间传输数据。我想在无限工作线程中使用在 GUI(主线程)中输入的数据。在我看过的所有教程中,数据只是从工作线程发送到主线程;就像通常的“加载栏”示例一样。

这是我目前所拥有的。该程序从 QT Designer 导入 GUI 输出,并在按下时将值打印到控制台。我还设置了一个无限线程,可以将“hello”打印到确实可以工作的控制台。只是为了让我指出正确的方向,当单击名为“pushbutton”的按钮时,您能告诉我如何将无限打印“hello”更改为其他内容吗?我熟悉 C/C++,但这是我的第一个 Python 项目,感谢任何帮助。

from PyQt4 import QtGui, QtTest, QtCore # Import the PyQt4 module we'll need
import sys # We need sys so that we can pass argv to QApplication

import gui # Import the GUI output from QT Designer


class GUI(QtGui.QMainWindow, gui.Ui_MainWindow):  # This class is to interact         with the GUI
def __init__(self):
    super(self.__class__, self).__init__()
    self.setupUi(self)  
    self.threadclass = ThreadClass()
    self.threadclass.start() # start thread class

    #########  Binds GUI widgets with functions  #####
    self.connectbutton.clicked.connect(self.pushbutton) # Call function "pushbutton" when pressed
    self.motor_up.clicked.connect(self.motorup) # Call function motorup when pressed
    self.motor_down.clicked.connect(self.motordown) # Call function motorup when pressed
    self.motor_speed_slider.valueChanged.connect(self.slider) # Call function slider
    self.auto_manual_check.stateChanged.connect(self.checkbox) # Call function checkbox
    self.Kp_box.valueChanged.connect(self.kp_entry)
    self.Ki_box.valueChanged.connect(self.ki_entry)
    self.Kd_box.valueChanged.connect(self.kd_entry)


    ######### Functions to run when GUI event is encountered

def pushbutton(self): # stuff to do if connect button is pressed                ## THIS BUTTON
    self.connectlabel.setText("Connected")
    QtTest.QTest.qWait(2000)        
    self.connectlabel.setText("Disconnected")
    self.emit(SIGNAL("text(Qstring)"),"it worked")

def slider(self): # Stuff to do when slider is changed
    print(self.motor_speed_slider.value()) # Print to monitor

def motorup(self): # Run if motor up is clicked
    print("motor up")

def motordown(self): # Run if motor down is clicked
    print("motor down")

def checkbox(self): # Run if checkbox is changed
    if self.auto_manual_check.isChecked():
        print("is checked")
    else:
        print("unchecked")

def kp_entry(self): # Run if kp is changed
    print(self.Kp_box.value())

def ki_entry(self): # Run if ki is changed
    print(self.Ki_box.value())

def kd_entry(self):# Run if kd is changed
    print(self.Kd_box.value())



class ThreadClass(QtCore.QThread): # Infinite thread to communicate with  robot
def __init__(self, parent = None):
    super(ThreadClass, self).__init__(parent)

def run(self):
    while 1:            # Runs continuously 
        print "hello"   #                           CHANGE THIS WHEN pushbutton IS PRESSED




def main():  # Function to run the main GUI
app = QtGui.QApplication(sys.argv)  # A new instance of QApplication
form = GUI()                 # We set the form to be our ExampleApp 
form.show()                         # Show the form
app.exec_()                         # and execute the app


if __name__ == '__main__':              # if we're running file directly and not importing it
main()                              # run the main function

编辑: 感谢您的快速和详细的答复。在尝试了您的解决方案后,我想我越来越接近了,但是我没有让它们为我的应用程序工作,我想我最初的问题可能不清楚。我本质上是在尝试在线程之间传递变量,实际打印只是为了表明变量已成功传输。我认为这个精简的代码会清除它。

from PyQt4 import QtGui, QtTest, QtCore # Import the PyQt4 module we'll need
import sys # We need sys so that we can pass argv to QApplication

import gui # Import the GUI output from QT Designer


class GUI(QtGui.QMainWindow, gui.Ui_MainWindow):            # This class is to interact with the GUI
def __init__(self):
    super(self.__class__, self).__init__()
    self.setupUi(self)  
    self.threadclass = ThreadClass()
    self.threadclass.start() 
    self.Kp_box.valueChanged.connect(self.kp_entry)     # Call function kp_entry if box value is changed

def kp_entry(self):                                     # Run if kp is changed                  
    print(self.Kp_box.value())                          # I WANT TO SEND THE BOX VALUE TO THE WORKER THREAD        

class ThreadClass(QtCore.QThread):                          # Infinite thread to communicate with robot
def __init__(self, parent = None):
    super(ThreadClass, self).__init__(parent)

def run(self):
    while 1:             
        print self.Kp_box.value()                       #CONTINUOUSLY PRINT THE UPDATED BOX VALUE    


def main():                             # Run the main GUI
   app = QtGui.QApplication(sys.argv)  # A new instance of QApplication
   form = GUI()                        # We set the form to be our ExampleApp 
   form.show()                         # Show the form
   app.exec_()                         # and execute the app

if __name__ == '__main__':              # if we're running file directly and not importing it
main()                              # run the main function

我要做的就是让用户在 GUI 中更新一个值,然后将该值传递给工作线程。在这种情况下,我有一个输入框,因此用户可以更改数字。然后我需要程序将该数字传输到将与机器人通信的工作线程。现在代码不起作用,我只是尽可能接近它以更好地展示我正在尝试做的事情。

如果有帮助,这是我的 GUIhere

【问题讨论】:

  • this 答案的 cmets 可能很有用(有一个解释了如何扩展信号/插槽连接以包含其他参数,以便将它们发送给工作人员)。

标签: python multithreading user-interface pyqt4 qt-designer


【解决方案1】:

有几种方法可以做到这一点。第一个(最简单的)是在你的方法pushbutton中放一行代码:

def pushbutton(self):
    self.threadclass.got_a_click()

给ThreadClass添加对应的方法和实例变量,并改变run方法:

def __init__(self):
   super(ThreadClass, self).__init__(parent)
   self.flag = False

def got_a_click(self):
    self.flag = True

def run(self):
    while not self.flag:
        print "hello"
    do_something_useful()

这可能已经足够了。请注意,got_a_click 方法在主循环上下文中运行,run 方法在辅助QThread 上下文中运行。

更复杂的方法是使用 Qt 的能力将信号发送到其他线程中的插槽。为此,您必须在辅助线程中运行 QEventLoop。 QThread 基类已经为此设置好了:它有一个run 方法,只包含一个事件循环。因此,为了拥有一个运行事件循环的 QThread 子类,只需不要覆盖 run 方法。这个 QThread 只会坐在那里什么都不做,直到一个 Signal 被发送到它的 Slot 之一。发生这种情况时,Slot 将在辅助线程中执行。代码如下所示:

class ThreadClass(QtCore.QThread): 
    def __init__(self, parent = None):
        super(ThreadClass, self).__init__(parent)
        self.moveToThread(self)

    def onButtonPress(self):
        pass  # Your code here

将此行添加到GUI的构造函数中: self.connectbutton.clicked.connect(self.threadclass.onButtonPress)

每次单击按钮时,onButtonPress 都会在辅助线程上下文中运行。

注意 ThreadClass 构造函数中的 self.moveToThread(self) 语句。这个非常重要。它告诉 Qt Signal/Slot 机制,该对象中的所有 Slot 都将通过 ThreadClass 线程中的事件循环来调用。没有它,Slot onButtonPress 将在主线程的上下文中运行,即使它的代码位于 ThreadClass 中。

另一个需要注意的陷阱是直接致电onButtonPress。那将 不使用 Signal/Slot 机制,onButtonPress 将像任何普通 Python 函数一样执行。例如,如果您从现有函数pushbutton 调用onButtonPress,它将在主线程上下文中运行。为了获得您想要的线程行为,您必须将 Qt 信号发送到 onButtonPress Slot。

这是您重新措辞的问题的答案。以下代码片段按您的意愿工作:

from PySide import QtGui, QtTest, QtCore # Import the PySide module we'll need
import sys

class GUI(QtGui.QMainWindow):            # This class is to interact with the GUI
    def __init__(self):
        super(self.__class__, self).__init__()
        self.Kp_box = QtGui.QSpinBox(self)
        self.threadclass = ThreadClass(self)
        self.threadclass.start() 
        self.Kp_box.valueChanged.connect(self.kp_entry)     # Call function kp_entry if box value is changed

    def kp_entry(self):                                     # Run if kp is changed                  
        print("Main", self.Kp_box.value())                          # I WANT TO SEND THE BOX VALUE TO THE WORKER THREAD        

class ThreadClass(QtCore.QThread):                          # Infinite thread to communicate with robot
    def __init__(self, main_window):
        self.main_window = main_window
        super(ThreadClass, self).__init__(main_window)

    def run(self):
        while 1:             
            print(self.main_window.Kp_box.value())                       #CONTINUOUSLY PRINT THE UPDATED BOX VALUE    


def main():                             # Run the main GUI
    app = QtGui.QApplication(sys.argv)  # A new instance of QApplication
    form = GUI()                        # We set the form to be our ExampleApp 
    form.show()                         # Show the form
    app.exec_()                         # and execute the app

if __name__ == '__main__':              # if we're running file directly and not importing it
    main()                              # run the main function

我对您的代码所做的更改:

  1. 我使用 PySide 而不是 PyQt,但它们大多兼容(或至少 他们应该是)。我不得不更改第一个导入 陈述;你必须把它改回来。
  2. 为了消除对 GUI 工具的依赖,我替换了你的 main 具有单个 SpinBox 的窗口。这表明 多线程问题的基本特征并将其解耦 从您的其他应用程序要求。
  3. 您的辅助线程无法访问 SpinBox,因为它没有 引用它的变量。我怀疑这是你的大问题 有。通过将主窗口对象传递给 ThreadClass 构造函数并将其存储在实例变量中。

绝不会从一个线程转移到另一个线程。这段代码简单地利用了 Python 程序中的所有变量都位于同一内存空间中的事实,即使该程序是多线程的。 [如果程序使用多个进程,那就不是这样了。]

这个小程序不提供任何优雅关闭的方式。

【讨论】:

  • 感谢 Paul 的精彩帖子!我已经尝试过您的解决方案,但取得的成功有限。当按下 GUI 按钮时,我已经能够告诉工作线程运行打印方法,但不能共享变量。我已经编辑了我的初步回复,以更好地说明我的目标。
  • 根据您重新设计的问题重新设计了我的答案 :-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-05
  • 2021-08-14
  • 2018-08-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多