代码之家  ›  专栏  ›  技术社区  ›  Justin Scott tvanfosson

wx.textcrl(或基础GTK+的多线程问题)

  •  1
  • Justin Scott tvanfosson  · 技术社区  · 5 年前

    我正在开发一个GUI来启动一个外部长期运行的后台程序。这个后台程序可以通过stdin得到输入命令,并使用stdout和stderr来保持输出和错误消息的打印。我在GUI中使用wx.textcrl对象来提供输入和打印输出。我目前的代码如下,这主要是受到“如何实现shell GUI窗口”帖子的启发: 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.textcrl或GTK+的底层GUI控件在多线程方面存在一些问题。有时我不输入任何输入命令,它也会崩溃。我从互联网上的某个帖子中搜索,看起来从第二个线程调用GUI控件很危险。

    我发现了我的错误。如中所述 wxpython -- threads and window events 或者在诺埃尔和罗宾的《Wxpython in action》一书的第18章中:

    最重要的一点是,GUI操作必须发生在主线程中,或者应用程序循环正在运行的线程中。在单独的线程中运行GUI操作是应用程序以不可预知和难以调试的方式崩溃的好方法…

    我的错误是我试图通过 wx.TextCtrl 对象到另一个线程。这是不对的。我将重新考虑我的设计。

    4 回复  |  直到 12 年前
        1
  •  1
  •   Marcelo Cantos    14 年前
        2
  •  1
  •   blaze    14 年前
        3
  •  1
  •   Satwik    14 年前

        4
  •  1
  •   Michael    12 年前

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