【问题标题】:TKinter GUI freezes until subprocess ends and realtime output to text WidgetTKinter GUI 冻结直到子进程结束并实时输出到文本小部件
【发布时间】:2019-08-25 07:12:32
【问题描述】:

我正在尝试向我不久前创建的 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(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

将非常缓慢地打印(我会慢慢说,因为一个简单的“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++;
     }
    }    
}

在一段时间内导致文本小部件内没有任何内容,一段时间后,输出突然出现。关于如何解决这个问题的任何想法?

【问题讨论】:

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


    【解决方案1】:

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

    【讨论】:

    • 您介意发布完整的答案吗?我不明白 endl 是什么
    【解决方案2】:

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

    for(int i = 0; i

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

    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() 由按钮调用或绑定到文本条目内的“返回”。

    【讨论】:

      【解决方案3】:

      这里的解决方案是使用线程,否则脚本会等到工作完成以使 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)
      

      【讨论】:

      • 我很久以前搜索过如何在python中使用线程,但是如果我没记错的话你不能终止线程,不是吗?出于这个原因,我在 GUI 中使用了多处理(我也尝试在进程中打开子进程,但结果很差)
      • 函数完成后线程终止。上周我不得不在 Tkinter GUI 中实现一个这样的控制台,并且线程工作就像一个魅力!
      • 那我试试,你有关于实时问题的线索吗?
      • 实时问题是由于我告诉你的,python 将忙于处理该过程,只有当它完成时,它才会继续主 tkinter 循环。对于 std 重定向,我使用了这个类(编辑了我的答案)。
      • 不幸的是,即使这样,程序也不能正常工作。如果您想检查,我使用示例程序编辑了我的问题。
      猜你喜欢
      • 2021-09-02
      • 2015-08-05
      • 1970-01-01
      • 2015-04-08
      • 2013-11-22
      • 1970-01-01
      • 2023-03-20
      • 1970-01-01
      • 2013-02-28
      相关资源
      最近更新 更多