Frage

I need to implement the following UI: - There is a window with a label "running experiment 1/X" and a button - When the window is loaded, some experiments are started. The experiments are run by os.system of subprocess.Popen, they are just pre-compiled C++ programs - The experiments should run strictly one after another and not simultaneously (hence I can't use subprocess.Popen) - The window should be active while the experiments are running and the user can press the button - When the button is pressed, the experiments stop (we can just wait until the current experiment ends and stop) and the window closes - When all experiments are over, the window should close itself

First I tried running experiments in threading.Thread, but it still blocked the window. So I switched to multiprocessing.Process:

class StoppableProcess(Process):
    def __init__(self, name, alg, proj, parent):
        Process.__init__(self)
        self.stop = False
        self.name = name
        self.alg = alg
        self.proj = proj
        self.parent = parent 

    def stop(self):
        self.stop = True

    def stopped(self):
        return self.stop

    def run(self):
        count = len([k for k in self.proj.values()])
        i = 1
        for p in self.proj.values():
            self.parent.label.setText("Running experiment " + str(i) + " / " + str(count))
            os.system("some command here")
            i += 1
            if self.stopped():
                break  
        self.parent.hide() 



class Runner(QDialog):
    def __init__(self, parent):
        QDialog.__init__(self, parent)
        self.layout = QVBoxLayout()
        self.label = QLabel("Running experiment 0 / 0")
        self.setWindowTitle("Running experiments")
        button = QPushButton("Break experiments")
        self.layout.addWidget(self.label)
        self.layout.addWidget(button)
        self.setLayout(self.layout)
        QObject.connect(button, SIGNAL("clicked()"), self.Break)

    def Run(self, name, alg, proj):
        self.thread = StoppableProcess(name, alg, proj, self)
        self.thread.start()
        self.show()
        self.thread.join()

    def Break(self):
        self.thread.stop()
        self.hide()

However, this doesn't work at all, apparently because the Runner object should be pickled to be passed to a subprocess, but pickling fails. I was thinking about avoiding passing the parent argument and using Qt signals instead, but maybe there's a better solution?

War es hilfreich?

Lösung

First of all, you can indeed use subprocess.Popen to start background processes and wait for their completion. See the documentation, specifically, the poll() method. Run the UI event loop until the process has exited.

Second, it is usually a good idea to avoid threads in Python. The multiprocessing module is mostly useful when you want to parallelize tasks written in Python. IMO, I think it is easier to use the subprocess module if you are just launching external child processes.

The following pseudocode illustrates the idea:

experiments = [...]
process = None

def start_next_experiment():
    if not experiments:
        print "Done!"
    else:
        experiment = experiments.pop()
        process = subprocess.Popen(experiment)

def on_start_clicked():
    start_next_experiment()

def on_stop_clicked():
     # Clear the queue
    experiments = []

    # optional: Kill the process
    if process:
        process.terminate()

def on_idle():
    if process:
        # use e.g. a PyQT timer to run this method periodically
        process.poll()
        if process.returncode is not None:
            process = None
            start_next_experiment()
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top