【发布时间】:2011-05-10 03:56:08
【问题描述】:
我正在开发一个 GUI 来启动一个外部长期运行的后台程序。这个后台程序可以通过 stdin 获得输入命令,并使用 stdout 和 stderr 继续打印输出和错误消息。我在 GUI 中使用 wx.TextCtrl 对象来提供输入和打印输出。我目前的代码如下,主要是受到“如何实现一个shell GUI窗口”帖子的启发:wxPython: how to create a bash shell window?
但是,我的以下代码使用“缓冲前一个输出”方法,即我使用线程来缓冲输出。只有当我给出下一个输入命令并按下“返回”按钮时,才能呈现缓冲的事务输出。现在,我想及时看到输出消息,因此我想具有“输出总是可以从后台子进程自发打印(直接刷新)并且我也可以通过标准输入干扰输入一些输入命令和打印输出。
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 操作是让您的应用程序在不可预知且难以预料的情况下崩溃的好方法- 调试方式...
我的错误是我试图将wx.TextCtrl 对象传递给另一个线程。这个不对。我会重新考虑我的设计。
【问题讨论】:
标签: python multithreading shell wxpython stdout