TKinter GUI 冻结,直到子进程结束并实时输出到文本小部件
TKinter GUI freezes until subprocess ends and realtime output to text Widget
我正在尝试向我之前创建的 GUI 添加一些功能,特别是我需要的功能是一个文本小部件,我发送的终端命令会在其中显示其输出。
重定向器 class 目前看起来像这样:
class StdRed(object):
def __init__(self, textwid):
self.text_space = textwid
def write(self, text):
self.text_space.config(state=NORMAL)
self.text_space.insert(END,text)
self.text_space.see(END)
self.text_space.update_idletasks()
self.text_space.config(state=DISABLED)
def flush(self):
pass
确实有效。我将 os.system(...) 命令替换为
打开终端命令
a = subprocess.Popen(command, stdout=PIPE, stderr=STDOUT, shell=True)
并且我通过 b = a.stdout.read()
读取标准输出,没有任何问题(不幸的是我需要 shell=True,否则我需要调用的一些程序会惨败)。
之后我尝试在 tkinter 文本小部件上进行实时输出,所以我更改了 b -->
while True:
b = a.stdout.readline().rstrip()
if not b:
break
print b
但似乎只有在调用进程结束时才会出现输出,即像
这样的简单C软件
for(int i = 0; i<100000; i++){
cout << i << '\n';}
将打印得非常慢(考虑到一个简单的 "ls" 命令也将非常缓慢地逐行打印,所以我说得很慢)for 循环结束时的所有数字。
除此之外,我注意到 GUI 被冻结,而通过子进程调用的程序是 运行。关于如何解决这些问题的任何想法?
编辑:
我创建了一个简单的终端,其中 运行 使用多处理 class 和 Popen 命令执行命令:
from Tkinter import *
from multiprocessing import Process, Pipe, Queue
import sys
from subprocess import PIPE, Popen, STDOUT
root = Tk()
root.title("Test Terminal")
root.resizable(False, False)
class StdRed(object):
def __init__(self, textwid):
self.text_space = textwid
def write(self, text):
self.text_space.config(state=NORMAL)
self.text_space.insert(END,text)
self.text_space.see(END)
self.text_space.update_idletasks()
self.text_space.config(state=DISABLED)
def flush(self):
pass
terminal = Frame(root, bd=2, relief=GROOVE)
terminal.grid(row=0, sticky='NSEW')
TERM = Label(terminal, text='TERMINAL', font='Helvetica 16 bold')
TERM.grid(row=0, pady=10, sticky='NSEW')
termwid = Text(terminal, height=10)
termwid.grid(row=1, sticky='NSEW')
termwid.configure(state=DISABLED, font="Helvetica 12")
sys.stdout = StdRed(termwid)
enter = StringVar()
enter.set("")
termen = Entry(terminal, textvariable=enter)
queue = Queue(maxsize=1)
a = None
def termexe(execute):
a = Popen(execute, shell=True, stdout=PIPE, stderr=STDOUT)
while True:
line = a.stdout.readline().rstrip()
if not line:
break
else:
queue.put(line)
queue.put('')
def labterm(thi):
if queue.empty():
if thi != None:
if thi.is_alive():
root.after(0,lambda:labterm(thi))
else:
pass
else:
pass
else:
q = queue.get()
print q
root.after(0,lambda:labterm(thi))
def comter(event=None, exe=None, seq=None):
global enter
if seq == 1:
if exe != None:
th = Process(target=termexe, args=(exe,))
th.daemon = True
th.start()
labterm(th)
th.join()
else:
pass
else:
if exe != None:
th = Process(target=termexe, args=(exe,))
th.daemon = True
th.start()
labterm(th)
else:
th = Process(target=termexe, args=(enter.get(),))
th.daemon = True
th.start()
enter.set('')
labterm(th)
def resetterm():
global termwid
termwid.config(state=NORMAL)
termwid.delete(1.0, END)
termwid.config(state=DISABLED)
termen.bind('<Return>', comter)
resterm = Button(terminal, text="Clear", command=resetterm)
terbut = Button(terminal, text="Command", command=comter)
termen.grid(row=2, sticky='NSEW')
terbut.grid(row=3, sticky='NSEW')
resterm.grid(row=4, sticky='NSEW')
root.mainloop()
问题是获取仍然不是实时的。
运行从软件中的入口程序:
#include <iostream>
using namespace std;
int main()
{
int i = 0;
while(1)
{
cout << i << '\n';
i++;
int a = 0;
while(a < 10E6)
{
a++;
}
}
}
导致文本小部件内暂时没有任何内容,一段时间后,输出突然出现。关于如何解决这个问题有什么想法吗?
这里的解决方案是使用线程,否则脚本会等到作业完成才能使 GUI 再次响应。有了线程,你的程序将运行同时是作业和GUI,代码示例:
import threading
def function():
pass
t = threading.Thread(target=function)
t.daemon = True # close pipe if GUI process exits
t.start()
我使用了这个标准重定向器:
class StdRedirector():
"""Class that redirects the stdout and stderr to the GUI console"""
def __init__(self, text_widget):
self.text_space = text_widget
def write(self, string):
"""Updates the console widget with the stdout and stderr output"""
self.text_space.config(state=NORMAL)
self.text_space.insert("end", string)
self.text_space.see("end")
self.text_space.config(state=DISABLED)
我尝试按照@Pau B 的建议使用线程(最后切换到多处理),我确实解决了 GUI 卡住的问题。现在的问题是 运行 程序
for(int i = 0; i<100000; i++){ cout << i << '\n';}
不是 return 实时输出,但它似乎经过缓冲,然后在一段时间后出现在文本小部件中。我使用的代码如下所示:
class StdRed(object):
def __init__(self, textwid):
self.text_space = textwid
def write(self, text):
self.text_space.config(state=NORMAL)
self.text_space.insert(END,text)
self.text_space.see(END)
self.text_space.update_idletasks()
self.text_space.config(state=DISABLED)
def termexe(execute):
a = Popen(execute, shell=True, stdout=PIPE, stderr=STDOUT)
while True:
line = a.stdout.readline().rstrip()
if not line:
break
else:
queue.put(line)
queue.put('')
def labterm():
global th
if queue.empty():
if th != None:
if th.is_alive():
root.after(0,labterm)
else:
pass
else:
pass
else:
q = queue.get()
print q
root.after(1,labterm)
def comter(event=None):
global enter
global th
if th != None:
if not th.is_alive():
th = Process(target=termexe, args=(enter.get(),))
th.start()
else:
pass
else:
th = Process(target=termexe, args=(enter.get(),))
th.start()
enter.set('')
labterm()
其中 comter() 由按钮调用或绑定到文本条目内的 'Return'。
也许这可以帮助其他人,我解决了用 endl 替换 '\n' 的问题。似乎 while 循环中的 cout 被缓冲并且仅在一段时间后调用 stdout flush,而使用 endl 函数在每个循环后调用
我正在尝试向我之前创建的 GUI 添加一些功能,特别是我需要的功能是一个文本小部件,我发送的终端命令会在其中显示其输出。 重定向器 class 目前看起来像这样:
class StdRed(object):
def __init__(self, textwid):
self.text_space = textwid
def write(self, text):
self.text_space.config(state=NORMAL)
self.text_space.insert(END,text)
self.text_space.see(END)
self.text_space.update_idletasks()
self.text_space.config(state=DISABLED)
def flush(self):
pass
确实有效。我将 os.system(...) 命令替换为
打开终端命令a = subprocess.Popen(command, stdout=PIPE, stderr=STDOUT, shell=True)
并且我通过 b = a.stdout.read()
读取标准输出,没有任何问题(不幸的是我需要 shell=True,否则我需要调用的一些程序会惨败)。
之后我尝试在 tkinter 文本小部件上进行实时输出,所以我更改了 b -->
while True:
b = a.stdout.readline().rstrip()
if not b:
break
print b
但似乎只有在调用进程结束时才会出现输出,即像
这样的简单C软件for(int i = 0; i<100000; i++){ cout << i << '\n';}
将打印得非常慢(考虑到一个简单的 "ls" 命令也将非常缓慢地逐行打印,所以我说得很慢)for 循环结束时的所有数字。 除此之外,我注意到 GUI 被冻结,而通过子进程调用的程序是 运行。关于如何解决这些问题的任何想法?
编辑:
我创建了一个简单的终端,其中 运行 使用多处理 class 和 Popen 命令执行命令:
from Tkinter import *
from multiprocessing import Process, Pipe, Queue
import sys
from subprocess import PIPE, Popen, STDOUT
root = Tk()
root.title("Test Terminal")
root.resizable(False, False)
class StdRed(object):
def __init__(self, textwid):
self.text_space = textwid
def write(self, text):
self.text_space.config(state=NORMAL)
self.text_space.insert(END,text)
self.text_space.see(END)
self.text_space.update_idletasks()
self.text_space.config(state=DISABLED)
def flush(self):
pass
terminal = Frame(root, bd=2, relief=GROOVE)
terminal.grid(row=0, sticky='NSEW')
TERM = Label(terminal, text='TERMINAL', font='Helvetica 16 bold')
TERM.grid(row=0, pady=10, sticky='NSEW')
termwid = Text(terminal, height=10)
termwid.grid(row=1, sticky='NSEW')
termwid.configure(state=DISABLED, font="Helvetica 12")
sys.stdout = StdRed(termwid)
enter = StringVar()
enter.set("")
termen = Entry(terminal, textvariable=enter)
queue = Queue(maxsize=1)
a = None
def termexe(execute):
a = Popen(execute, shell=True, stdout=PIPE, stderr=STDOUT)
while True:
line = a.stdout.readline().rstrip()
if not line:
break
else:
queue.put(line)
queue.put('')
def labterm(thi):
if queue.empty():
if thi != None:
if thi.is_alive():
root.after(0,lambda:labterm(thi))
else:
pass
else:
pass
else:
q = queue.get()
print q
root.after(0,lambda:labterm(thi))
def comter(event=None, exe=None, seq=None):
global enter
if seq == 1:
if exe != None:
th = Process(target=termexe, args=(exe,))
th.daemon = True
th.start()
labterm(th)
th.join()
else:
pass
else:
if exe != None:
th = Process(target=termexe, args=(exe,))
th.daemon = True
th.start()
labterm(th)
else:
th = Process(target=termexe, args=(enter.get(),))
th.daemon = True
th.start()
enter.set('')
labterm(th)
def resetterm():
global termwid
termwid.config(state=NORMAL)
termwid.delete(1.0, END)
termwid.config(state=DISABLED)
termen.bind('<Return>', comter)
resterm = Button(terminal, text="Clear", command=resetterm)
terbut = Button(terminal, text="Command", command=comter)
termen.grid(row=2, sticky='NSEW')
terbut.grid(row=3, sticky='NSEW')
resterm.grid(row=4, sticky='NSEW')
root.mainloop()
问题是获取仍然不是实时的。 运行从软件中的入口程序:
#include <iostream>
using namespace std;
int main()
{
int i = 0;
while(1)
{
cout << i << '\n';
i++;
int a = 0;
while(a < 10E6)
{
a++;
}
}
}
导致文本小部件内暂时没有任何内容,一段时间后,输出突然出现。关于如何解决这个问题有什么想法吗?
这里的解决方案是使用线程,否则脚本会等到作业完成才能使 GUI 再次响应。有了线程,你的程序将运行同时是作业和GUI,代码示例:
import threading
def function():
pass
t = threading.Thread(target=function)
t.daemon = True # close pipe if GUI process exits
t.start()
我使用了这个标准重定向器:
class StdRedirector():
"""Class that redirects the stdout and stderr to the GUI console"""
def __init__(self, text_widget):
self.text_space = text_widget
def write(self, string):
"""Updates the console widget with the stdout and stderr output"""
self.text_space.config(state=NORMAL)
self.text_space.insert("end", string)
self.text_space.see("end")
self.text_space.config(state=DISABLED)
我尝试按照@Pau B 的建议使用线程(最后切换到多处理),我确实解决了 GUI 卡住的问题。现在的问题是 运行 程序
for(int i = 0; i<100000; i++){ cout << i << '\n';}
不是 return 实时输出,但它似乎经过缓冲,然后在一段时间后出现在文本小部件中。我使用的代码如下所示:
class StdRed(object):
def __init__(self, textwid):
self.text_space = textwid
def write(self, text):
self.text_space.config(state=NORMAL)
self.text_space.insert(END,text)
self.text_space.see(END)
self.text_space.update_idletasks()
self.text_space.config(state=DISABLED)
def termexe(execute):
a = Popen(execute, shell=True, stdout=PIPE, stderr=STDOUT)
while True:
line = a.stdout.readline().rstrip()
if not line:
break
else:
queue.put(line)
queue.put('')
def labterm():
global th
if queue.empty():
if th != None:
if th.is_alive():
root.after(0,labterm)
else:
pass
else:
pass
else:
q = queue.get()
print q
root.after(1,labterm)
def comter(event=None):
global enter
global th
if th != None:
if not th.is_alive():
th = Process(target=termexe, args=(enter.get(),))
th.start()
else:
pass
else:
th = Process(target=termexe, args=(enter.get(),))
th.start()
enter.set('')
labterm()
其中 comter() 由按钮调用或绑定到文本条目内的 'Return'。
也许这可以帮助其他人,我解决了用 endl 替换 '\n' 的问题。似乎 while 循环中的 cout 被缓冲并且仅在一段时间后调用 stdout flush,而使用 endl 函数在每个循环后调用