我正在开发一个GUI来启动外部长期运行的后台程序。该后台程序可以通过 stdin 给出输入命令,并使用 stdout 和 stderr 来保持打印输出和错误消息。我在 GUI 中使用 wx.TextCtrl 对象来提供输入和打印输出。我当前的代码如下,主要受到“如何实现shell GUI窗口”帖子的启发:wxPython:如何创建 bash shell 窗口? https://stackoverflow.com/questions/989129/wxpython-how-to-create-a-bash-shell-window
但是,我的以下代码使用“缓冲先前的输出”方法,即我使用线程来缓冲输出。仅当我给出下一个输入命令并按“返回”按钮时,才能呈现缓冲的交易输出。现在,我希望及时看到输出消息,因此我希望具有“输出始终可以从后台子进程自发打印(直接刷新)”的功能,并且我还可以通过 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——线程和窗口事件 https://stackoverflow.com/questions/1496092/wxpython-threads-and-window-events或者在 Noel 和 Robin 所著的《WxPython in action》一书中的第 18 章中:
最重要的一点是 GUI 操作必须在主线程中进行,或者在应用程序循环运行的线程中进行。在单独的线程中运行 GUI 操作是避免应用程序在不可预测且难以修复的情况下崩溃的好方法。调试方法...
我的错误是我试图通过wx.TextCtrl
对象到另一个线程。这个不对。我会重新考虑我的设计。