wx.TextCtrl(或底层GTK +)的多线程问题

时间:2010-11-15 10:23:58

标签: python multithreading shell wxpython stdout

我正在开发一个GUI来启动一个外部长期运行的后台程序。该后台程序可以通过stdin给出输入命令,并使用stdout和stderr来保持打印输出和错误消息。我在GUI中使用wx.TextCtrl对象来提供输入和打印输出。我目前的代码如下,主要受“如何实现shell GUI窗口”帖子的启发:wxPython: how to create a bash shell window?

但是,我的下面的代码使用“buffer previous output”方法,即我使用一个线程来缓冲输出。只有当我给出下一个输入命令并按下“返回”按钮时,才会呈现缓冲的事务输出。现在,我希望及时看到输出消息,因此我希望具有“输出始终可以自动打印出来(直接刷出)来自后台子进程的功能,我还可以通过stdin干扰输入一些输入命令打印输出。

class BashProcessThread(threading.Thread):
    def __init__(self, readlineFunc):
        threading.Thread.__init__(self)
        self.readlineFunc = readlineFunc
        self.lines = []
        self.outputQueue = Queue.Queue()
        self.setDaemon(True)

    def run(self):
        while True:
           line = self.readlineFunc()
           self.outputQueue.put(line)
           if (line==""):
            break
        return ''.join(self.lines)

    def getOutput(self):
        """ called from other thread """            
        while True:
            try:
                line = self.outputQueue.get_nowait()
                lines.append(line)
            except Queue.Empty:
                break
        return ''.join(self.lines)

class myFrame(wx.Frame):
    def __init__(self, parent, externapp):
        wx.Window.__init__(self, parent, -1, pos=wx.DefaultPosition)
        self.textctrl = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER|wx.TE_MULTILINE)
        launchcmd=["EXTERNAL_PROGRAM_EXE"]
        p = subprocess.Popen(launchcmd, stdin=subprocess.PIPE, 
                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        self.outputThread = BashProcessThread(p.stdout.readline)
        self.outputThread.start()
        self.__bind_events()         
        self.Fit()

    def __bind_events(self):
        self.Bind(wx.EVT_TEXT_ENTER, self.__enter)

    def __enter(self, e):
        nl=self.textctrl.GetNumberOfLines()
        ln =  self.textctrl.GetLineText(nl-1)
        ln = ln[len(self.prompt):]     
        self.externapp.sub_process.stdin.write(ln+"\n")
        time.sleep(.3)
        self.textctrl.AppendText(self.outputThread.getOutput())

我应该如何修改上面的代码来实现这个目标?我还需要使用线程吗?我可以按如下方式编写一个线程吗?

class PrintThread(threading.Thread):
    def __init__(self, readlineFunc, tc):
        threading.Thread.__init__(self)
        self.readlineFunc = readlineFunc
        self.textctrl=tc
        self.setDaemon(True)

    def run(self):
        while True:
            line = self.readlineFunc()
            self.textctrl.AppendText(line)

然而,当我尝试使用上面的代码时,它会崩溃。

我有来自Gtk的错误,如下所示。

(python:13688): Gtk-CRITICAL **: gtk_text_layout_real_invalidate: assertion `layout->wrap_loop_count == 0' failed
Segmentation fault

或有时错误

 (python:20766): Gtk-CRITICAL **: gtk_text_buffer_get_iter_at_mark: assertion `GTK_IS_TEXT_MARK (mark)' failed
Segmentation fault

或有时错误

(python:21257): Gtk-WARNING **: Invalid text buffer iterator: either the iterator is uninitialized, or the characters/pixbufs/widgets in the buffer have been modified since the iterator was created.
You must use marks, character numbers, or line numbers to preserve a position across buffer modifications.
You can apply tags and insert marks without invalidating your iterators,
but any mutation that affects 'indexable' buffer contents (contents that can be referred to by character offset)
will invalidate all outstanding iterators
Segmentation fault

或有时错误

Gtk-ERROR **: file gtktextlayout.c: line 1113 (get_style): assertion failed: (layout->one_style_cache == NULL)
aborting...
Aborted

或其他错误信息,但每次都有不同的错误信息,真的很奇怪!

似乎wx.TextCtrl或GTK +的底层gui控件在多线程方面存在一些问题。有时我不输入任何输入命令,它也会崩溃。我从互联网上的某个帖子中搜索,看起来从辅助线程调用GUI控件是危险的。

我发现了自己的错误。正如wxpython -- threads and window events或Noel和Robin所着的“WxPython in action”第18章所指出的那样:

  

最重要的一点是GUI操作必须在主线程中运行,或者运行应用程序循环的GUI操作。在单独的线程中运行GUI操作是一个很好的方法,使您的应用程序崩溃在不可预测和困难 - 调试方式......

我的错误是我试图将wx.TextCtrl对象传递给另一个线程。这个不对。我会重新考虑我的设计。

4 个答案:

答案 0 :(得分:1)

最大的问题是你不能强制子进程不缓冲它的输出,并且当stdout是一个管道时,大多数程序的标准I / O库将缓冲输出(更确切地说,它们将来自行 - 缓冲以阻止缓冲)。像Expect这样的工具通过在伪tty中运行子进程来解决这个问题,这基本上欺骗了子进程,使其认为其输出将转到终端。

有一个名为Pexpect的Python模块,它以与Expect相同的方式解决了这个问题。我从来没有使用它,所以警告经纪人

答案 1 :(得分:1)

pty.spawn()可能有用。您也可以使用pty.openpty()手动创建PTY,并将它们作为stdin / stdout传递给popen。

如果您可以访问文本模式程序源,也可以在那里禁用缓冲。

答案 2 :(得分:1)

你不能将TextCtrl对象传递给OutputThread并让它直接将文本附加到输出而不用__enter方法绑定它吗?

答案 3 :(得分:1)

gtk(构建wxpython并且断言失败的东西)应用程序的任何调用契约是 必须 修改gui控件来自主要的线程。所以答案很简单:

在C#中,我需要执行以下操作(这是一个匿名的lambda函数,几乎可以肯定在wxpython / gtk中有类似的库调用)

Gtk.Application.Invoke((_,__) =>
{
    //code which can safely modify the gui goes here
});

这应该解决你的断言问题......它至少对我有用。