Python:如何将多进程链接到多进度条

1 投票
1 回答
1415 浏览
提问于 2025-04-17 21:51

看起来我快要实现将所有由子进程的 Pool() 启动的子进程与 PyQt 的进度条连接起来了。这里有个示例代码。显然,有一些问题阻碍了我这个梦想的实现。

这个概念:

externalFunc() 负责执行所有主要任务。它会不时地将“进度值”发送给 MainWindow() 实例,通过将其值存储到 poolDict 变量中,poolDict 是这样声明的:

poolDict=manager.dict()

与此同时,myEvenListener() 方法在后台运行,等待 VALUE 被“发布”。一旦 VALUE 被“接收”,myEvenListener() 就会更新进度条到接收到的 VALUE,并将其重置为零(以避免重复更新进度条)。在尝试更新进度条之前,myEvenListener(self) 会先检查进度条是否已经达到了最大值 100,只有在确认没有达到后才会继续,使用的是:

if pb.value()>=100: continue

不幸的是,即使 externalFunc() 提供了足够多的值(160),进度条也从未达到 100。此外,无法在 MainWindow() 的循环中退出。

在你阅读这段代码之前,请小心运行,因为它可能会在你的机器上生成多个 Python 进程,这些进程需要被终止。

问题:

  1. 为什么进度条从未达到最大值 100?
  2. 如何确保 myEvenListener() 方法在不需要时不再运行?
  3. 如何确保所有子进程在以下情况下都能结束: a. 所有进度条都达到 100% b. 用户关闭对话框或终止主进程。

import sys, time
from PyQt4 import QtCore, QtGui

import multiprocessing as mp
from multiprocessing import Pool
manager = mp.Manager()

poolDict=manager.dict()

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(each):
    for i in range(16):
        print i
        poolDict[each]=10+i 
        time.sleep(0.5)


class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        # self.myList=[.1, .3, .5, 1.0, 1.5, 2.9, 3.1]
        self.myList=[1]
        self.main_layout = QtGui.QVBoxLayout()
        self.pBars={}
        self.state=True

        for each in self.myList:
            pb=PbWidget(total=101)
            self.main_layout.addWidget(pb)
            self.pBars[each]={'pb':pb, 'value':0, 'total_value':0} 

        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 myEvenListener(self):
        """This function runs at the background as an infinite while loop. It constantly reads 
        a value stored in poolDict variable to which externalFunc() writes a new value every second. 
        The value represents a 'Progress' and needs to be passes to Progress Bar widget to which only MainWindow() 
        class has an access. After a Value was read it is used to update a Progress Bar. 
        After a progress bar was updated the Value is reset to zero. 
        The Problem: 'if pb.value()>=100' statement is used to make sure the function doesn't loop if the ProgressBar
        is already reached 100. By some reason the bar never reaches its maximum 100 even while externalFunc() calls
        enough times to reach this number. An ussue # 2: There is no meachanism to exit this while loop without adding
        three more variables... Can it be achieved with what we already have? 
        """
        while self.state:    
            for each in self.pBars:
                pb = self.pBars[each]['pb']
                print "\n Current Bar value =", pb.value()
                if pb.value()>=100:
                    print 'skipping'
                    continue

                value=None
                if each in poolDict.keys(): 
                    # read delivered value
                    delivered_value=poolDict[each]
                    # reset delivered value
                    poolDict[each]=0
                    # update bar with delivered value
                    if ( 101-pb.value() ) < delivered_value:
                        print "\n\t UPDATING WITH "
                        pb.update_bar( 101-pb.value() )
                        print "\n\t AFTER ", pb.value()
                    else:
                        pb.update_bar( delivered_value )
                    # print '\n\t\t\t  Updating bar using this value', delivered_value


    def OK(self):
        pool = Pool(processes=3)
        pool.map_async( externalFunc, self.myList)
        self.myEvenListener()

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

1 个回答

2

在这里输入图片描述

这是一个修改过的代码。看起来运行得很好,而且相当稳定。

import sys, time
from PyQt4 import QtCore, QtGui

import multiprocessing as mp
from multiprocessing import Pool
manager = mp.Manager()
poolDict=manager.dict()

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(each):
    for i in range(16):
        value =10+i
        poolDict[each]=value    
        time.sleep(each)


class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.myList=[.5, .8, 1.2, 1.8, 2.2, .3, .1]

        self.main_layout = QtGui.QVBoxLayout()
        self.pBars={}
        self.state=True
        self.name=None

        for each in self.myList:
            pb=PbWidget(total=101)
            self.main_layout.addWidget(pb)
            self.pBars[each]={'pb':pb, 'name':each} 

        ok_button = QtGui.QPushButton("Distribute")
        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 myEvenListener(self):
        """This function runs at the background as an infinite loop. It is constantly reading
        a value stored in poolDict variable to which externalFunc() writes a new value. 
        The value represents a 'Progress' and needs to be passed to Progress Bar widget to which MainWindow() 
        class has an access. After a Value was read and used to update a Progress Bar it is reset to zero.       
        """ 
        entities = self.pBars.keys()
        while self.state:    
            for each in entities:
                if each not in self.pBars.keys():   continue    

                pb = self.pBars[each]['pb']
                if pb.value()>=100:
                    self.pBars.pop(each, None)

                value=None
                if each in poolDict.keys():
                    # read delivered value
                    delivered_value=poolDict[each]
                    # reset delivered value
                    poolDict[each]=0
                    # update bar with delivered value
                    if ( 101-pb.value() ) < delivered_value:
                        pb.update_bar( 101-pb.value() )                     
                    elif delivered_value>0:
                        pb.update_bar( delivered_value )

                if len(self.pBars.keys())==0:
                    self.state=False

    def OK(self):       
        pool = Pool(processes=3)
        pool.map_async( externalFunc, self.myList)
        self.myEvenListener()

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

撰写回答