проблема многопоточности с wx.TextCtrl (или лежащим в основе GTK +) - PullRequest
2 голосов
/ 15 ноября 2010

Я разрабатываю графический интерфейс для запуска внешней долгосрочной фоновой программы.Этой фоновой программе можно дать команду ввода через stdin и использовать stdout и stderr для продолжения распечатки выходных сообщений и сообщений об ошибках.Я использую объект wx.TextCtrl внутри графического интерфейса для ввода и вывода на печать.Мой текущий код выглядит следующим образом, что в основном вдохновлено постом «Как реализовать окно графического интерфейса оболочки»: wxPython: как создать окно оболочки bash?

Однако мои следующиеКод использует подход «буферизовать предыдущий вывод», т.е. я использую поток для буферизации вывода.Вывод буферизованной транзакции может быть обработан только тогда, когда я дам следующую команду ввода и нажму кнопку «возврат».Теперь я хочу, чтобы выводимые сообщения были своевременными, поэтому я хочу иметь функцию, заключающуюся в том, что «вывод всегда может быть самопроизвольно распечатан (непосредственно удален) из фонового подпроцесса, и я также мог бы мешать вводить некоторую команду ввода через 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 из вторичного потока.

Я нашел свою ошибку.Как указано в wxpython - потоки и события окна или в главе 18 книги «WxPython в действии» Ноэля и Робина:

Наиболее важным моментом является то, что GUIоперации должны выполняться в главном потоке или в том цикле, в котором выполняется цикл приложения. Выполнение операций с графическим интерфейсом в отдельном потоке является хорошим способом сбоя приложения непредсказуемым и трудным для отладки способом ...

Моя ошибка в том, что я пытался передать объект wx.TextCtrl в другой поток.Это неправильно.Я пересмотрю свой дизайн.

Ответы [ 4 ]

1 голос
/ 29 октября 2012

Контракт вызова для всего в gtk (на котором построен wxpython и приложение, которое не подтверждает утверждение) заключается в том, что вы должны только изменить элементы управления графического интерфейса из MainГи нить.Поэтому ответ для меня прост:

В C # мне нужно было сделать следующее (анонимная лямбда-функция, и почти наверняка есть аналогичный вызов библиотеки в wxpython / gtk)

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

И это должно решить ваши проблемы с утверждениями ... по крайней мере, для меня.

1 голос
/ 15 ноября 2010

pty.spawn () может быть полезным.Также вы можете вручную создавать PTY с помощью pty.openpty () и передавать их в popen как stdin / stdout.

Если у вас есть доступ к источнику программы в текстовом режиме, вы также можете отключить буферизациюесть.

1 голос
/ 15 ноября 2010

Не могли бы вы просто передать объект TextCtrl в свой объект OutputThread и напрямую добавить текст в вывод, не связывая его методом __enter?

1 голос
/ 15 ноября 2010

Самая большая проблема заключается в том, что вы не можете заставить подпроцесс не буферизовать свои выходные данные, и стандартная библиотека ввода / вывода большинства программ будет буферизовать выходные данные, когда stdout представляет собой канал (точнее, они будут идти из строки буферизация для блокировки буферизации). Такие инструменты, как Expect, исправляют это, выполняя подпроцесс в псевдо-tty, что в основном заставляет подпроцесс думать, что его вывод идет в терминал.

Существует модуль Python под названием Pexpect , который решает это так же, как и Expect. Я никогда не использовал его, поэтому caveat emptor .

...