Python:使用多进程池在外部函数更新控件

1 投票
2 回答
1442 浏览
提问于 2025-04-17 21:22

我希望你能告诉我,为什么按下“确定”按钮后,文本框里的内容没有更新。

from PyQt4 import QtCore, QtGui

def externalFunc(arg):
    print '\n\t Accessing lineedit from outside. Result   "', jobDialog.lineEdit.text(), '"   OK'
    print "Attempting to change the lineEdit field to", arg
    jobDialog.lineEdit.setText(arg)
    print "...Completed."

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.main_layout = QtGui.QVBoxLayout()

        self.lineEdit=QtGui.QLineEdit('Initial Text')
        self.main_layout.addWidget(self.lineEdit)

        ok_button = QtGui.QPushButton("OK")
        ok_button.clicked.connect(self.OK)
        self.main_layout.addWidget(ok_button)       

        central_widget = QtGui.QWidget()
        central_widget.setLayout(self.main_layout)
        self.setCentralWidget(central_widget)

    def OK(self):
        myList=['One','Two','Three']
        from multiprocessing import Pool
        pool = Pool(processes=10)
        try: pool.map_async( externalFunc, myList)
        except Exception, e: print e

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)

    jobDialog = MainWindow()
    jobDialog.resize(480, 320)
    jobDialog.show()
    sys.exit(app.exec_())

另一个例子,这次是进度条。

这个例子不仅没有更新,还导致整个程序崩溃。还是同样的问题:为什么会这样,怎么解决呢。

from PyQt4 import QtCore, QtGui

class PbWidget(QtGui.QProgressBar):
    def __init__(self, parent=None, total=20):
        super(PbWidget, self).__init__()
        self.setMinimum(1)
        self.setMaximum(total)        
        self._active = False  

    def update_bar(self, to_add_number):
        while True:
            time.sleep(0.01)
            value = self.value() + to_add_number            
            self.setValue(value)
            QtGui.qApp.processEvents()
            if (not self._active or value >= self.maximum()):                
                break
        self._active = False

    def closeEvent(self, event):
        self._active = False

def externalFunc(arg=None):
    print "Attempting to update Progress Bar "
    jobDialog.pb.update_bar(10)
    print "...Completed."

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.main_layout = QtGui.QVBoxLayout()

        self.pb=PbWidget(total=101)
        self.main_layout.addWidget(self.pb)

        ok_button = QtGui.QPushButton("OK")
        ok_button.clicked.connect(self.OK)
        self.main_layout.addWidget(ok_button)       

        central_widget = QtGui.QWidget()
        central_widget.setLayout(self.main_layout)
        self.setCentralWidget(central_widget)

    def OK(self):
        # externalFunc()
        myList=[1,2,3]
        from multiprocessing import Pool
        pool = Pool(processes=10)
        pool.map_async( externalFunc, myList)

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)

    jobDialog = MainWindow()
    jobDialog.resize(480, 320)
    jobDialog.show()
    sys.exit(app.exec_())

2 个回答

2

这个问题出现的原因是,不同的程序之间通常不共享内存。所以,一个程序不能直接更新另一个程序的界面元素。即使你真的共享了内存,你的程序也会经常崩溃,因为Qt不允许多个线程或进程同时访问QObjects。

你需要做的是,让你的子进程向主进程的主线程发送消息,然后在主线程中处理这些消息,并更新界面元素。

4

这里是我找到的一个有效解决方案的描述。

通过 multiprocessing 的 Pool 方法(比如 map_async())启动的子进程,无法与主进程中声明的变量(对象)进行通信。主进程就是所有子进程的来源。如果我们只想发送任务去处理,这可能没问题。但通常我们需要从子进程那里获得“实时”的反馈(在它们处理任务时)。为了让子进程能够访问一个在主进程和所有子进程之间“可见”的变量,我们使用 multiprocessing 的 Manager()。语法非常简单:

from multiprocessing import Manager
manager = Manager()

在声明了一个 Manager() 的实例(这里叫做 manager)后,我们就可以声明一个变量……目前我知道的有字典(dict)和列表(list)类型。使用的语法如下:

myDict=manager.dict()

除了存储和获取数据,这些变量还可以用来设置/读取/重置用于启动/停止 while 循环函数的真/假标志(类似事件监听器)。这个想法是我们在后台运行一个 while 循环,它不断监听(监控)这种真或假变量的状态。这里是更新进度条的修订代码,进度条由通过 multiprocessing Pool() 的 map_async() 方法启动的子进程更新。

from PyQt4 import QtCore, QtGui

from multiprocessing import Process, Manager, Pool
manager = Manager()
myDict=manager.dict()
myDict['state'] = True
myDict['value'] = 0


class PbWidget(QtGui.QProgressBar):
    def __init__(self, parent=None, total=20):
        super(PbWidget, self).__init__()
        self.setMinimum(1)
        self.setMaximum(total)        
        self._active = False  

    def update_bar(self, to_add_number):
        while True:
            time.sleep(0.01)
            value = self.value() + to_add_number            
            self.setValue(value)
            QtGui.qApp.processEvents()
            if (not self._active or value >= self.maximum()):                
                break
        self._active = False

    def closeEvent(self, event):
        self._active = False




def externalFunc(arg=None):
    print "\t\t Attempting to update Progress Bar by changing dictionary values"
    myDict['value']=arg
    myDict['state']=True

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.progressBarCurrentValue=0
        self.main_layout = QtGui.QVBoxLayout()

        self.pb=PbWidget(total=101)
        self.main_layout.addWidget(self.pb)

        ok_button = QtGui.QPushButton("OK")
        ok_button.clicked.connect(self.OK)
        self.main_layout.addWidget(ok_button)       

        central_widget = QtGui.QWidget()
        central_widget.setLayout(self.main_layout)
        self.setCentralWidget(central_widget)


    def OK(self):
        myList=[10,10,10,10,10,10,10,10,10,10,10,10,10]
        pool = Pool(processes=10)
        pool.map_async( externalFunc, myList)

    def EventListener(self):
        while myDict['state']:              
            value=myDict['value']
            self.pb.update_bar(value)

            self.progressBarCurrentValue=self.pb.value()
            print "...running sicne current Progress Bar value is < 99:", self.progressBarCurrentValue, myDict['state']

            if self.progressBarCurrentValue>99: 
                myDict['state'] = False
                print "...stopping"



if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)    
    jobDialog = MainWindow()
    jobDialog.resize(480, 320)
    jobDialog.show()
    jobDialog.EventListener()
    sys.exit(app.exec_())

撰写回答