简体   繁体   English

Python PyQt4线程,共享变量

[英]Python PyQt4 Threading, sharing variables

I have been researching this topic for the past few days and have not found a suitable answer for my application. 过去几天,我一直在研究此主题,但找不到适合我的应用程序的答案。 I am building a GUI in python 2.7 with PyQt4 to interact with a robot. 我正在使用PyQt4在python 2.7中构建GUI以与机器人进行交互。 I need one thread(main) to run the GUI created with QT Designer and another thread(worker) to continuously transmit data to and from the robot. 我需要一个线程(主线程)来运行使用QT Designer创建的GUI,而另一个线程(工作人员)则需要不断地与机器人传输数据。 I want to use data entered in the GUI(main thread) in the infinite worker thread. 我想在无限工作线程中使用在GUI(主线程)中输入的数据。 In all of the tutorials I have seen, data is only sent from the worker to the main thread; 在我所看到的所有教程中,数据仅从工作线程发送到主线程。 like the usual "loading bar" example. 就像通常的“加载栏”示例一样。

This is what I have so far. 到目前为止,这就是我所拥有的。 This program imports the GUI output from QT Designer and prints values to the console when they are pressed. 该程序从QT Designer导入GUI输出,并在按下时将值打印到控制台。 I also have an infinite thread set up that prints "hello" to the console which does work. 我还设置了一个无限线程,该线程将“ hello”打印到可以正常工作的控制台。 Just to get me pointed in the right direction, can you show me how to change the infinite print "hello" to something else when the the button called "pushbutton" is clicked? 只是为了使我指向正确的方向,您能告诉我如何在单击名为“ pushbutton”的按钮时将无限打印“ hello”更改为其他内容吗? I am familiar with C/C++ but this is my first Python project and any help is appreciated. 我熟悉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

EDIT: Thank you for the quick and detailed responses. 编辑:感谢您的快速和详细的答复。 After experimenting with your solutions I think I'm getting closer, however I'm not getting them to work for my application, I think I may have been unclear in my initial question. 在尝试了您的解决方案之后,我认为我已经接近了,但是我没有让他们为我的应用程序工作,我想我最初的问题可能不清楚。 I am essentially trying to pass variables between threads, the actual printing was just to show that the variable was successfully transferred. 我本质上是试图在线程之间传递变量,实际的打印只是为了表明变量已成功传输。 I think this stripped down code will clear it up. 我认为这精简的代码将清除它。

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

All I want to do is enable the user to update a value in the GUI and then pass that value to the worker thread. 我要做的就是使用户能够在GUI中更新值,然后将该值传递给工作线程。 In this case, I have an entry box so the user can change the number. 在这种情况下,我有一个输入框,以便用户可以更改号码。 I then need the program to transfer that number to the worker thread which will communicate with the robot. 然后,我需要程序将该数字传输到将与机器人通信的工作线程。 Right now the code does not work, I just got it as close as I could to better show what I'm trying to do. 现在代码不起作用,我只是尽可能地接近它,以更好地显示我要执行的操作。

If it helps, this is my GUI here 如果有帮助,这是我的GUI 这里

There are a couple of ways to do this. 有两种方法可以做到这一点。 The first (simplest) one is to put a line of code in your method pushbutton : 第一个(最简单的)方法是在方法pushbutton放入一行代码:

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

Add a corresponding method and instance variable to ThreadClass, and change the run method: 将相应的方法和实例变量添加到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()

This may be good enough. 这可能已经足够了。 Note that the method got_a_click runs in the main loop context, and the run method is in the secondary QThread context. 请注意,方法got_a_click在主循环上下文中run ,而run方法在辅助QThread上下文中。

A more sophisticated approach is to use Qt's ability to send Signals to Slots in other threads. 一种更复杂的方法是利用Qt的功能将信号发送到其他线程中的插槽。 In order to do that, you must run a QEventLoop in the secondary thread. 为此,必须在辅助线程中运行QEventLoop。 The QThread base class is already set up for this: it has a run method that consists of just an event loop. QThread基类已经为此设置:它具有一个run方法,该方法仅包含一个事件循环。 So in order to have a QThread subclass that runs an event loop, simply don't override the run method. 因此,为了使QThread子类运行事件循环,只需不要覆盖run方法。 This QThread will just sit there doing nothing until a Signal gets sent to one of its Slots. 这个QThread只会坐在那里什么也不做,直到一个Signal被发送到它的一个插槽中。 When that happens the Slot will execute in the secondary thread. 发生这种情况时,插槽将在辅助线程中执行。 The code looks like this: 代码如下:

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

    def onButtonPress(self):
        pass  # Your code here

Add this line to the constructor of GUI : self.connectbutton.clicked.connect(self.threadclass.onButtonPress) 将此行添加到GUI的构造函数中: self.connectbutton.clicked.connect(self.threadclass.onButtonPress)

Each time you click the button, onButtonPress runs in the secondary thread context. 每次单击按钮时, onButtonPress都会在辅助线程上下文中运行。

Note the statement self.moveToThread(self) in the ThreadClass constructor. 请注意ThreadClass构造函数中的语句self.moveToThread(self) This is very important. 这个非常重要。 It tells the Qt Signal/Slot mechanism that all the Slots in this object are to be invoked through the event loop in the ThreadClass thread. 它告诉Qt Signal / Slot机制,该对象中的所有Slot都将通过ThreadClass线程中的事件循环被调用。 Without it, the Slot onButtonPress would run in the context of the main thread, even though its code is located in ThreadClass . 没有它,Slot onButtonPress将在主线程的上下文中运行,即使其代码位于ThreadClass

Another pitfall to watch out for is calling onButtonPress directly. 注意的另一个陷阱是直接调用onButtonPress That will not use the Signal/Slot mechanism, and onButtonPress will execute like any ordinary Python function. 那将不会使用Signal / Slot机制,并且onButtonPress将像任何普通的Python函数一样执行。 For example, if you called onButtonPress from your existing function pushbutton it would run in the main thread context. 例如,如果您从现有功能pushbutton调用onButtonPress ,它将在主线程上下文中运行。 In order to get the threading behavior you desire, you must cause a Qt Signal to be emitted to the onButtonPress Slot. 为了获得所需的线程行为,必须使Qt信号发射到onButtonPress插槽。

Here is the answer to your re-phrased question. 这是您重新措辞的问题的答案。 The following code fragment works as you intend: 下面的代码片段按预期工作:

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

What I changed from your code: 我对您的代码所做的更改:

  1. I use PySide not PyQt but they're mostly compatible (or at least they're supposed to be). 我使用PySide而不是PyQt,但是它们大多数兼容(或者至少应该是)。 I had to change the first import statement; 我不得不更改第一个导入语句; you will have to change it back. 您将不得不将其改回。
  2. To remove the dependency on the GUI tool, I replaced your main window with one that has a single SpinBox. 为了消除对GUI工具的依赖,我将主窗口替换为具有单个SpinBox的主窗口。 That demonstrates the essential features of your multithreading problem and decouples it from your other application requirements. 这说明了多线程问题的基本功能,并将其与其他应用程序需求分离了。
  3. Your secondary thread could not access the SpinBox because it has no variable that references it. 您的辅助线程无法访问SpinBox,因为它没有引用它的变量。 I suspect that's the big problem you're having. 我怀疑这是您遇到的最大问题。 It's fixed by passing the main window object to the ThreadClass constructor and storing it in an instance variable. 通过将主窗口对象传递给ThreadClass构造函数并将其存储在实例变量中,可以修复此问题。

In no sense is anything being transferred from one thread to another. 从任何意义上讲,任何事物都不会从一个线程转移到另一线程。 This code simply uses the fact that all the variables in a Python program live in the same memory space, even if the program is multi-threaded. 此代码仅使用以下事实:即使程序是多线程的,Python程序中的所有变量都位于同一内存空间中。 [That wouldn't be true if the program used multiple processes .] [如果程序使用多个进程,那将不是真的。]

This little program doesn't provide any way of closing gracefully. 这个小程序没有提供任何正常关闭的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM