pexpect 执行长时间运行的 bash 命令时挂起 wxpython GUI

0 投票
2 回答
672 浏览
提问于 2025-04-18 12:54

我正在编写一个图形界面软件,这个软件有一个终端窗口(wxCtrl),可以实时显示外部程序的输出。

我尝试过使用subprocess.Popen,但效果不如预期,因为它会在运行时让我的图形界面卡住,只有在执行完成后才会显示输出。

def miExecuteCmd(self, cmd):
    self.terminal.addText("\n###\n\n")
    self.terminal.addText("Executing: %s\n" % cmd)
    args = shlex.split(cmd)
    p = subprocess.Popen(args, stdout = subprocess.PIPE)
    output = p.stdout.readlines()
    output = "".join(output)
    self.terminal.addText(output)
    if (p.returncode != None and p.returncode != 0 ):
        self.terminal.addText("Command Execution Problem, return code is %d\n" % p.returncode)
    return output 

现在我在尝试使用pexpect,我看了这篇文章,如何使用pexpect获取子进程的即时输出

所以我写了类似这样的代码,

def miExecuteCmd(self, cmd):
    self.terminal.addText("\n###\n\n")
    self.terminal.addText("Executing: %s\n" % cmd)
    output = []
    child = pexpect.spawn(cmd)
    while True:
        try:
            child.expect('\n')
            line = child.before
            output.append(line)
            self.terminal.addText(line)
        except pexpect.EOF:
            break 
    if child.exitstatus != None and child.exitstatus != 0:
        line = "Command Execution Problem, return code is %d\n" % child.exitstatus
        self.terminal.addText(line)
        output.append(line)

    output = "".join(output)

    return output

但即使这样,当我使用一个运行时间很长的命令时,图形界面还是会卡住。

所以我想找一个简单的pexpect解决方案,能让我在操作图形界面的同时看到命令的输出。

我看了pexpect的文档,似乎pexpect.spawn()应该会为命令启动一个独立的线程,现在我有点困惑,不知道是否应该把pexpect.spawn()放在一个新线程里。

2 个回答

1

无论你用什么方法来运行脚本,你的图形界面窗口都会卡住。为了避免这种情况,你需要把命令放在一个单独的线程中执行,这样图形界面就不会被阻塞。如果你能提供一个简单的代码示例,那就更好了。不过,试试下面的做法:

import thread

def miExecuteCmd(self, cmd):
    #bunch of codes...

def on_execute(self, cmd):
    thread.start_new_thread(self.miExecutecmd, ())

把你的事件处理器绑定到调用 self.on_execute,这样它就会在一个新线程中执行命令。

0

最后,我找到了解决我问题的好办法,解决了在wxPython中界面卡顿和线程之间的通信。

大家应该看看这篇文章 http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads,它结合了线程、wx.CallAfter()和PubSub来解决线程通信的问题。所以在我的情况下,只需要添加pexpect来指示需要沟通的内容。

下面是我的run()示例,你需要查看上面链接中的示例。

def run(self):
        wx.CallAfter(self.sendToMainThread, "\n###\n\n")
        text = "Executing: %s\n" % (self.cmd)
        wx.CallAfter(self.sendToMainThread, text)

        child = pexpect.spawn(self.cmd) 
        while True:
            try:
                if self.stopFlag:
                    line = "Stop Buttont Clicked, Stopping External Command... \n"
                    wx.CallAfter(self.sendToMainThread, line)
                    child.terminate(True)
                    child.close()
                    break
                child.expect('\n')
                line = child.before
                wx.CallAfter(self.sendToMainThread, line)
            except pexpect.EOF:
                child.close()
                break
        if child.exitstatus != None and child.exitstatus != 0:
            line = "Command Execution Problem, return code is %d\n" % child.exitstatus
            wx.CallAfter(self.sendToMainThread, line)
        #it looks like next line never happens because of the exception handling above.
        #child.close() make sure child return a code.
        elif child.exitstatus == None:
            line = "Command Execution was interrupted.\n"
            wx.CallAfter(self.sendToMainThread, line)

        #sending an end signal to main thread. command is finished.
        wx.CallAfter(Publisher().sendMessage, "endthread", True)

撰写回答