TKinter GUI冻结,直到子进程结束并实时输出到文本小部件

时间:2019-04-03 17:01:50

标签: python user-interface tkinter subprocess python-multiprocessing

我正在尝试向前一段时间创建的GUI添加一些功能,特别是我需要的功能是文本小部件,我发送的终端命令在其中显示其输出。 目前,重定向器类如下所示:

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(命令,stdout = PIPE,stderr = STDOUT,shell = True)

,并且我通过以下方式读过stdout: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被冻结了。关于如何解决这些问题的任何想法?

编辑

我创建了一个简单的终端,该终端使用多处理类和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++;
     }
    }    
}

导致文本小部件内无任何显示,一段时间后,输出突然出现。关于如何解决此问题的任何想法?

3 个答案:

答案 0 :(得分:1)

这里的解决方案是使用线程,否则脚本将等到作业完成后再次使GUI响应。使用线程,您的程序将同时运行作业和GUI,代码示例:

POST _bulk
{ "index" : { "_index" : "test", "_type" : "_doc", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_type" : "_doc", "_id" : "2" } }
{ "create" : { "_index" : "test", "_type" : "_doc", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_type" : "_doc", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }

我使用了此标准重定向程序:

import threading

def function():
    pass

t = threading.Thread(target=function)
t.daemon = True # close pipe if GUI process exits
t.start()

答案 1 :(得分:0)

我尝试使用@Pau B建议的线程(最后切换到多处理),确实解决了GUI卡住的问题。现在的问题是运行程序

  

for(int i = 0; i <100000; i ++){cout << i <<'\ n';}

不会返回实时输出,但似乎已被缓冲,并在一段时间后显示在文本小部件中。我正在使用的代码如下:

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”。

答案 2 :(得分:0)

也许这可以帮助其他人,我解决了用endl替换'\ n'的问题。似乎while循环中的cout已缓冲,并且stdout flush仅在一段时间后才被调用,而使用endl时,该函数在每个周期后才被调用