我花了大约6个小时来堆栈溢出,重写我的python代码并尝试让它工作.它只是没有.不管我做什么.
目标:
使子流程的输出实时显示在tkinter文本框中.
问题:
我无法弄清楚如何让Popen实时工作.它似乎一直挂起,直到该过程完成. (单独运行,该过程完全按预期工作,所以只有这个有错误)
相关代码:
import os
import tkinter
import tkinter.ttk as tk
import subprocess
class Application (tk.Frame):
process = 0
def __init__ (self, master=None):
tk.Frame.__init__(self, master)
self.grid()
self.createWidgets()
def createWidgets (self):
self.quitButton = tk.Button(self, text='Quit', command=self.quit)
self.quitButton.grid()
self.console = tkinter.Text(self)
self.console.config(state=tkinter.DISABLED)
self.console.grid()
def startProcess (self):
dir = "C:/folder/"
self.process = subprocess.Popen([ "python", "-u", dir + "start.py" ], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE, cwd=dir)
self.updateLines()
def updateLines (self):
self.console.config(state=tkinter.NORMAL)
while True:
line = self.process.stdout.readline().decode().rstrip()
if line == '' and self.process.poll() != None:
break
else:
self.console.insert(tkinter.END, line + "\n")
self.console.config(state=tkinter.DISABLED)
self.after(1, self.updateLines)
app = Application()
app.startProcess()
app.mainloop()
另外,如果写得不好,请随意破坏我的代码.这是我的第一个python项目,我不希望对该语言有任何好处.
解决方法:
这里的问题是process.stdout.readline()将阻塞,直到有一整行可用.这意味着在进程退出之前,将永远不会满足条件行==”.你有两个选择.
首先,您可以将stdout设置为非阻塞并自行管理缓冲区.它看起来像这样.编辑:正如Terry Jan Reedy所指出的那样,这只是一个Unix解决方案.第二种选择应该是首选.
import fcntl
...
def startProcess(self):
self.process = subprocess.Popen(['./subtest.sh'],
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
bufsize=0) # prevent any unnecessary buffering
# set stdout to non-blocking
fd = self.process.stdout.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
# schedule updatelines
self.after(100, self.updateLines)
def updateLines(self):
# read stdout as much as we can
line = ''
while True:
buff = self.process.stdout.read(1024)
if buff:
buff += line.decode()
else:
break
self.console.config(state=tkinter.NORMAL)
self.console.insert(tkinter.END, line)
self.console.config(state=tkinter.DISABLED)
# schedule callback
if self.process.poll() is None:
self.after(100, self.updateLines)
第二种方法是让一个单独的线程将行读入队列.然后从队列中弹出更新行.它看起来像这样
from threading import Thread
from queue import Queue, Empty
def readlines(process, queue):
while process.poll() is None:
queue.put(process.stdout.readline())
...
def startProcess(self):
self.process = subprocess.Popen(['./subtest.sh'],
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
self.queue = Queue()
self.thread = Thread(target=readlines, args=(self.process, self.queue))
self.thread.start()
self.after(100, self.updateLines)
def updateLines(self):
try:
line = self.queue.get(False) # False for non-blocking, raises Empty if empty
self.console.config(state=tkinter.NORMAL)
self.console.insert(tkinter.END, line)
self.console.config(state=tkinter.DISABLED)
except Empty:
pass
if self.process.poll() is None:
self.after(100, self.updateLines)
穿线路线可能更安全.我不肯定将stdout设置为非阻塞将适用于所有平台.