2
\$\begingroup\$

I think I have fixed the issues which caused this program to not be functional.

Now I believe the design of my code is somewhat bad and may be causing problems with the number of processes that are created (I would think 10 processes would be created when 20 are?). I am unsure what may be causing this, but I probably have lots of other issues in cleanliness of my code as well.

What I am Trying to do:

I am trying to get a GUI to allocate processes to a bunch of tabs (number of tabs is determined by the user in the real program) and then having each tab display results of each process in real time.
My hope is to have the user be able to start/stop a long process on each tab's data at will, and then be able to manually change the data in between starting/stopping the automated data manipulation process.

Here is a simple script:

from PyQt4 import QtCore, QtGui
import multiprocessing as mp
import sys
import time

# Just make some data to manipulate and process
def make_data():
    data = {'num': 3, 'num2': 4}
    return data

# Some function which tries to process the data
# which can be stopped and allows the adjustment of the data by the user
def f(data, can_run):
    for i in range(data['num'], data['num'] + 10**5):
        can_run.wait()
        data['num'] += 1
        data['num2'] += 100

# Each tab holds a set of data which shows the manipulation of it's contents
# can be started and stopped to adjust the data with a button
class Tab(QtGui.QTabWidget):
    def __init__(self, parent, data_init):
        # GUI junk
        QtGui.QTabWidget.__init__(self, parent)
        self.top_level_layout = QtGui.QGridLayout(self)
        self.frame = QtGui.QFrame(self)
        self.top_level_layout.addWidget(self.frame)
        self.step_label = dict()
        self.step_stacked = dict()
        self.step_text = dict()
        self.step_input = dict()
        for wdgts in data_init.keys():
            self.step_label[wdgts] = QtGui.QLabel(str(wdgts))
            self.step_stacked[wdgts] = QtGui.QStackedWidget()
            self.step_text[wdgts] = QtGui.QLabel(str(data_init[wdgts]))
            self.step_input[wdgts] = QtGui.QLineEdit()
            self.step_stacked[wdgts].addWidget(self.step_text[wdgts])
            self.step_stacked[wdgts].addWidget(self.step_input[wdgts])
            self.top_level_layout.addWidget(self.step_stacked[wdgts])
            self.top_level_layout.addWidget(self.step_label[wdgts])

        # Each tab gets a button to start or stop a process
        self.process_button = QtGui.QPushButton("Process")
        self.top_level_layout.addWidget(self.process_button, 1, 1)
        QtCore.QObject.connect(self.process_button, QtCore.SIGNAL("clicked()"), self.start_process)

        # setup a managet to hold the data to pass between the GUI and the process
        self.manager = mp.Manager()
        self.data = self.manager.dict(make_data())
        self.Event = self.manager.Event()
        self.process = mp.Process(target=f, args=(self.data,self.Event,))
        self.process.daemon = True

        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.update_GUI)
        self.first_start = True

    def update_GUI(self):
        try:
            for wdgt in self.data.keys():
                self.step_label[wdgt].setText(str(wdgt))
                self.step_input[wdgt].setText(str(self.data[wdgt]))
                self.step_text[wdgt].setText(str(self.data[wdgt]))       
        except EOFError:
            print 'have reached the end of the file'
            QtCore.QObject.disconnect(self.process_button, QtCore.SIGNAL("clicked()"), self.stop_process)
            QtCore.QObject.disconnect(self.process_button, QtCore.SIGNAL("clicked()"), self.start_process)
            self.timer.stop()
            self.process.join()
        return

    def start_process(self):
        for wdgt in self.step_stacked.keys():
            self.step_stacked[wdgt].setCurrentWidget(self.step_text[wdgt])
        if self.first_start==True:
            print 'first start'
            self.process.start()
            self.first_start = False
        else:
            for wdgt in self.data.keys():
                self.data[wdgt] = int(self.step_input[wdgt].text())
            print 'start process again'
        self.process_button.setText('Stop Processing - (manually adjust data)')
        QtCore.QObject.disconnect(self.process_button, QtCore.SIGNAL("clicked()"), self.start_process)
        QtCore.QObject.connect(self.process_button, QtCore.SIGNAL("clicked()"), self.stop_process)
        self.timer.start(100)
        self.Event.set()
        return

    def stop_process(self):
        print 'stopping proccess'
        self.Event.clear()
        for wdgt in self.step_stacked.keys():
            self.step_stacked[wdgt].setCurrentWidget(self.step_input[wdgt])
        self.timer.stop()
        self.process_button.setText('Start Processing Again Using Data')
        QtCore.QObject.disconnect(self.process_button, QtCore.SIGNAL("clicked()"), self.stop_process)
        QtCore.QObject.connect(self.process_button, QtCore.SIGNAL("clicked()"), self.start_process)
        return

# More GUI stuff
class MainWindow(QtGui.QMainWindow):
    def __init__(self, parent = None):
        QtGui.QMainWindow.__init__(self)
        screen_height = app.desktop().screenGeometry().height()
        screen_width = app.desktop().screenGeometry().width()
        self.resize(int(screen_width*0.2), int(screen_height*0.2))
        self.tab_list = []
        self.setTabShape(QtGui.QTabWidget.Rounded)
        self.centralwidget = QtGui.QWidget(self)
        self.top_level_layout = QtGui.QGridLayout(self.centralwidget)
        self.tabWidget = QtGui.QTabWidget(self.centralwidget)
        self.top_level_layout.addWidget(self.tabWidget, 1, 0, 25, 25)
        self.setCentralWidget(self.centralwidget)
        self.centralwidget.setLayout(self.top_level_layout)
        self.process_all__button = QtGui.QPushButton("Start All Processes")
        self.top_level_layout.addWidget(self.process_all__button, 0, 0)
        QtCore.QObject.connect(self.process_all__button, QtCore.SIGNAL("clicked()"), self.start_all_process)
        # Make Tabs in loop from button
        for i in range(0,10):
            super_cool_data = make_data()
            name = 'tab ' + str(i)
            self.tab_list.append(Tab(self.tabWidget, super_cool_data))
            self.tabWidget.addTab(self.tab_list[-1], name)

    # All Processes can be started and stopped together
    def start_all_process(self):
        self.process_all__button.setText('Stop All Processing')
        QtCore.QObject.disconnect(self.process_all__button, QtCore.SIGNAL("clicked()"), self.start_all_process)
        QtCore.QObject.connect(self.process_all__button, QtCore.SIGNAL("clicked()"), self.stop_all_process)
        for i in self.tab_list:
            i.start_process()

    def stop_all_process(self):
        self.process_all__button.setText('Start All Processing')
        QtCore.QObject.disconnect(self.process_all__button, QtCore.SIGNAL("clicked()"), self.stop_all_process)
        QtCore.QObject.connect(self.process_all__button, QtCore.SIGNAL("clicked()"), self.start_all_process)
        for i in self.tab_list:
            i.stop_process()

    # Get rid of the processes after closing the GUI, no matter where they are in process stage
    def closeEvent(self, event):
        for tab in self.tab_list:
            tab.process.terminate()

if __name__ == "__main__":
    app = QtGui.QApplication([])
    win = MainWindow()
    win.show()
    sys.exit(app.exec_())
\$\endgroup\$

closed as off-topic by Adam Nov 22 '13 at 8:18

This question appears to be off-topic. The users who voted to close gave this specific reason:

  • "Your question must contain working code for us to review it here. For questions regarding specific problems encountered while coding, try Stack Overflow. After getting your code to work, you may edit this question seeking a review of your working code." – Adam
If this question can be reworded to fit the rules in the help center, please edit the question.

1
\$\begingroup\$

This question appears to be off topic for Code Review, because the code does not work.


(Regarding original revision:)
But anyway, I don't think using psutils to suspend a subprocess is a good idea. That could freeze the pipe in way that disturbs the main process. Instead, look into the synchronization primitives provided by multiprocessing.
(Regarding current revision:)

You are using Event the wrong way. wait() waits while the event is in clear state, so the the event should be set when the subprocess may run.

Start the subprocess in Tab.__init__, set event in start_process, clear it in stop_process and change some_complex_processing to

def some_complex_processing(data, can_run):
    for i in range(data['num'], data['num'] + num):
        can_run.wait()
        data['num'] = i
        data['num2'] = i+100
        time.sleep(0.1)

The sleep is there to prevent this demo from using much CPU. Feel free to reduce and eventually eliminate the sleep when you get everything working.

After the above changes the start/stop all processes button seems to work fine. The button inside the tab stops working after a couple of clicks, however, but that seems unrelated to multiprocessing.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the help! Sorry if this was out of place, I will edit the question after it's working so that it fits. I will check out the synchronization primitives and hopefully something will work out well. \$\endgroup\$ – chase Nov 22 '13 at 15:11
  • \$\begingroup\$ I added a multiprocessing.Manager() which allows a much simpler design by sharing the data between the Tab and the process. However, I don't see any method of pausing/resuming the execution of the process such that the user can manipulated the shared data and then resubmit the data to the process from where it left off. Am I missing something? \$\endgroup\$ – chase Nov 22 '13 at 23:14
  • \$\begingroup\$ @chase You can use an Event to signal the worker process that it is allowed to run. The worker needs to check the state periodically by calling .wait(). \$\endgroup\$ – Janne Karila Nov 25 '13 at 9:56
  • \$\begingroup\$ I added the code with the Event in my question, but it's still not working so well. It doesn't appear to be updating the values at all and the start/stop only works some of the time. \$\endgroup\$ – chase Nov 26 '13 at 4:12
  • \$\begingroup\$ Thank you for your help, it is much appreciated! I made the changes to the script; although, as you stated, it is still not responding so well to button clicks. \$\endgroup\$ – chase Nov 26 '13 at 17:52

Not the answer you're looking for? Browse other questions tagged or ask your own question.