0

I have a program that call to an subprogram. While the subprogram is running with Popen, I need the run button to be disable and the stop button to enable. However, because Popen opens a new process, things that are suppose to print after the program has finish will print out right away. I tried adding self.p.communicate() after Popen, but the GUI will freeze until the subprogram finish running, hence the stop button will not work. Here is my program:

def gui(QtGui.QWidget):
    ...
    self.runButton = QtGui.QPushButton('Run')
    self.stopButton = QtGui.QPushButton('Stop')

    self.runButton.clicked.connect(self.run)
    self.stopButton.clicked.connect(self.kill)

    self.runButton.setEnabled(True)
    self.stopButton.setEnabled(False)

def run(self):
    self.runButton.setEnabled(False)
    self.p = subprocess.Popen(sample.exe)

    #self.p.communicate()

    self.stopButton.setEnabled(True)   
    print "Finish running"  #without communicate() it will print out before .exe finish running

def kill(self):
    self.p.kill()
    self.stopButton.setEnabled(False)
    print 'You have stop the program'
    self.runButton.setEnabled(True)

I'm using Window7, Python 2.7, pyqt4. I do not have to use subprocess, anything that open and able kill subprogram will be fine.

Thanks in advance.

Edit: Tried using QProcess as dano suggested. I have added the following codes to my program:

def gui(QtCore.Widget):
    self.process = QtCore.QProcess(self)    

def run(self):
    self.process.start(exe_path)
    self.process.started.connect(self.onstart)
    self.process.finished.connect(self.onfinish)
    self.runButton.setEnabled(False)

    #self.p = subprocess.Popen(sample.exe)   #removed
    #self.p.communicate()                    #removed

def onstart (self):
    self.stopButton.setEnabled(True)     
    self.runButton.setEnabled(False)
    print "Started\n"

def onfinish (self):
    self.stopButton.setEnabled(False)     
    self.runButton.setEnabled(True)
    print "Finish running\n"

def kill(self):
    self.process.kill()
    #self.p.kill()                           #removed

Here is the output:

Click "Run" (output when subprogram finish running)

Finish running

Click "Run" second time

Started

Click "Stop"

Finish running
Finish running

Click "Run" third time

Started
Started

Click "Stop"

Finish running
Finish running
Finish running

Something happened here.

jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • To clarify, you want your program to run in the background, and you want its stdout to be printed, but only after the program has finished running? – dano Jan 29 '15 at 16:55
  • Sorry, I don't really understand your question. If you mean that my program (GUI)'s stdout printed after the executable(someone else's) finish running. Then yes. – user3335341 Jan 29 '15 at 17:11
  • Ah, so you don't care about the stdout of the subprocess. You just don't want "Finish running" to be printed until the subprocess is actually completed, right? – dano Jan 29 '15 at 17:13
  • Yes, exactly like that. – user3335341 Jan 29 '15 at 17:17
  • 1
    Looks like you can do this using `QProcess`. See here: http://stackoverflow.com/questions/15804325/qt-qpushbutton-is-blocked-by-child-process. If that answers your question, I'll mark it as a dupe. – dano Jan 29 '15 at 17:28
  • How do you kill the `QProcess`? Also, out of curiosity, if I do need real time stdout from the subprogram, would `QProcess` still work? – user3335341 Jan 29 '15 at 18:35
  • 1
    According to [the docs for `QProcess`](http://qt-project.org/doc/qt-4.8/qprocess.html#terminate), it has a `terminate()` method you can use to kill the process. I've never used a `QProcess` before, but it looks like you can use the `readChannel()` method to get an object that will let you call things like `readLine`, etc. on it, which would give you real-time stdout. – dano Jan 29 '15 at 19:48
  • 2
    You should not connect your signals to slots when the "run" button is clicked, but when the `QProcess` is instantiated. As it stands now, when you click "Run" the second time, it is connecting the signals again, and so the methods `onStart` and `onFinish` are connected multiple times and are thus running multiple times. – three_pineapples Jan 29 '15 at 22:18

1 Answers1

0

p = subprocess.Popen(exe) does not wait for exe to finish -- it returns as soon as exe is started.

p.communicate() does wait for the child process to end and therefore it blocks your GUI thread (GUI freezes until the subprocess exits).

To avoid blocking the GUI, you could check whether p.poll() returns None to find out whether p process is still running. Usually a GUI framework provides a way to set up periodic callbacks (e.g., QTimer for Qt) -- you can call p.poll() there. Or put the blocking call (such as p.wait()) into a background thread and notify GUI thread on completion (emit signal, generate an event).

QProcess suggested by @dano already has the notification mechanism built-in (finished signal).

As @three_pineapples said: do not connect the same signal multiple times if you don't want the callbacks to be called multiple times. There is no point in your case to call .start() on the same process instance more than once:

def run(self):
    self.runButton.setEnabled(False) # call it first because 
                                     # processes are not started instantly
    self.process = QtCore.QProcess(self) 
    self.process.started.connect(self.onstart) # connect "start" signal
    self.process.finished.connect(self.onfinish)
    self.process.start(exe_path, args) # then start
Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670