问题
I have a need from a Tkinter GUI to be able to start a long-running Linux script but at the same time I want to be able to have a stop-button enable so I can stop the process. Neither Tkinter nor popen are threadsafe. I thought of simply either placing the popen function in a thread or possibly just enabling a button in a thread. I am currently using Red Hat Linux 5.9 which uses Python 2.4.3, but I have later versions available online that I could use. In the program, below note that I reconfigure the start button to a stop button, but that does not work because the start button function is active, but it indicates my intent. Note that the stop function simply does an os.kill() on the child.
#!/usr/bin/python
import subprocess
import sys
import Tkinter
import tkMessageBox
import signal
import os
import time
class popentest:
def __init__(self):
self.mainWindow = Tkinter.Tk()
def __createUI(self, mainWindow):
mainWindow.protocol("WM_DELETE_WINDOW", self.OnExitWindow)
## Local variables.
sleep=5
iter=5
self.pid=0
self.mainWindow.title("Test popen")
self.start=Tkinter.Button(mainWindow, text=u"Start", command=self.onStart)
self.start.grid()
self.kwit = Tkinter.Button(mainWindow,text=u"Quit !",
command=self.onQuit)
self.kwit.grid()
self.lIter=Tkinter.Label(mainWindow, text="Iterations: ")
self.iterE=Tkinter.Entry(mainWindow, width=2)
self.lSleep = Tkinter.Label(mainWindow, text="Sleep time")
self.sleepEntry = Tkinter.Entry(mainWindow, width=3)
self.lIter.grid()
self.iterE.grid()
self.lSleep.grid()
self.sleepEntry.grid()
self.iterE.insert(0, str(iter))
self.sleepEntry.insert(0,str(sleep))
def startPopen(self):
self.__createUI(self.mainWindow)
self.mainWindow.mainloop()
def execute(self, numIters, sleep):
self.p = subprocess.Popen(['./testpopen.sh',str(numIters), str(sleep)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
self.pid=self.p.pid
print str(self.p.pid)+" started"
for line in iter(self.p.stdout.readline, ''):
print line
self.p.stdout.close()
self.pid=0
self.start.configure(text=u"Start", command=self.onStart)
def onStart(self):
numIters=self.iterE.get()
sleep=self.sleepEntry.get()
if not numIters.isdigit():
tkMessageBox.showerror(
"invalid entry",
"Iteration (%s)must be numeric\n" % numIters)
return
elif not sleep.isdigit():
tkMessageBox.showerror(
"invalid entry",
"Sleep(%s) is not numeric\n" % sleep)
return
numIters=int(numIters)
sleep=int(sleep)
if numIters <= 0 or sleep <=0 :
tkMessageBox.showerror(
"invalid entry",
"Either iteration (%d) or sleep(%d) is <= 0\n" % (numIters, sleep))
else:
print "configuring start to stop"
self.start.configure(text=u"Stop", command=self.onStop)
time.sleep(1)
self.execute(numIters, sleep)
def onStop(self):
print "configuring stop to start"
os.kill(p.pid, signal.SIGTERM)
self.start.configure(text=u"Start", command=self.onStart)
def OnExitWindow(self):
if self.pid != 0 :
os.kill(self.pid, signal.SIGKILL)
self.mainWindow.destroy()
def onQuit(self):
if self.pid != 0 :
os.kill(self.pid, signal.SIGKILL)
self.mainWindow.destroy()
if __name__ == "__main__":
remote = popentest()
remote.startPopen()
回答1:
You can start your process using Popen, using a non-blocking pipe to communicate with the process - this way, you can receive its output asynchronously. I already used an enhanced version of Popen, code was from an ActiveState Python cookbook recipe. I could not find the recipe on the web anymore, but as I still have the code I pasted it here:
https://gist.github.com/mguijarr/6874724
Then, in your Tkinter code, you can use a timer to check periodically for the state of the process (terminated, or not) and to get output:
self.p = EnhancedPopen.Popen(['./testpopen.sh',str(numIters), str(sleep)],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True,universal_newlines=True)
self.mainWindow.after(100, self.check_process)
def check_process(self):
# get stdout output
output = EnhancedPopen.recv_some(self.p, e=0, stderr=0)
...
if self.p.poll() is not None:
# process terminated
...
return
# set timer again (until process exits)
self.mainWindow.after(100, self.check_process_output)
来源:https://stackoverflow.com/questions/19234104/tkinter-popen-and-threads