wxpython多线程_关于python:wx.TextCtrl(或基础GTK +)的多线程问题

我正在开发GUI,以启动外部长期运行的后台程序。可以通过stdin给该后台程序输入命令,并使用stdout和stderr继续打印输出和错误消息。我在GUI内使用wx.TextCtrl对象提供输入和打印输出。我当前的代码如下,其主要灵感来自"如何实现shell GUI窗口"一文:wxPython:如何创建bash shell窗口?

但是,我的以下代码使用"缓冲先前的输出"方法,即,我使用线程来缓冲输出。只有当我给出下一个输入命令并按"返回"按钮时,才可以呈现缓冲的事务输出。现在,我想及时看到输出消息,因此,我希望具有这样的功能:"始终可以从后台子进程中自发打印输出(直接冲洗掉),并且还可以通过stdin和打印输出。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49class 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+"

")

time.sleep(.3)

self.textctrl.AppendText(self.outputThread.getOutput())

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

1

2

3

4

5

6

7

8

9

10

11class 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的错误,如下所示。

1

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

Segmentation fault

有时甚至是错误

1

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

Segmentation fault

有时甚至是错误

1

2

3

4

5

6(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

有时甚至是错误

1

2

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

aborting...

Aborted

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

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

我发现了我的错误。正如wxpython-线程和窗口事件或Noel和Robin在" WxPython in action"一书的第18章中所指出的那样:

The most important point is that GUI operations must take place in the main thread, or the one that the application loop is running in. Running GUI operations in a separate thread is a good way for your application to crash in unpredictable and hard-to-debug ways...

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

gtk(构建wxpython以及失败断言的事物)应用程序中任何内容的调用合同是,您只能修改Main Gui线程中的gui控件。所以对我来说答案很简单:

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

1

2

3

4Gtk.Application.Invoke((_,__) =>

{

//code which can safely modify the gui goes here

});

那应该理清您的断言问题……至少对我而言。

确实; wxpython中的方法是wx.CallAfter(请参阅在调用app.MainLoop()之后更新wxPython进度栏)。

难道您只是将TextCtrl对象传递给OutputThread并让它直接将文本附加到输出中,而不用__enter方法绑定它吗?

您好,Satwik,此解决方案不正确。这是一个错误,请参阅我对原始帖子的进一步评论。

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

如果您有权访问文本模式程序源,则也可以在此处禁用缓冲。

嗨,大火,谢谢您的输入。但是我的问题是在gui里面有一个嵌入式cli(wx.textctrl代表的地方),以便用户与后台进程进行交互。我不知道您的答案是否解决了这一点。另外,缓冲来自我的线程,而不是后台程序。

您确定要在应用程序中进行缓冲吗?通常,此症状是子进程中缓冲的标志,而不是读缓冲。无论如何,如果它的读者缓冲忘记了readline(),请使用read1()(而不是read()!)并自己组装行。

嗨,烈火,我的意思是我创建了readlinefunc或BashProcessThread来缓冲外部应用程序(EXTERNAL_PROGRAM_EXE)的输出。我使用这种方式进行用户交互。但我的方式是不可取的。我需要某种方式可以自动刷新事务输出,而无需等待下一个输入/"返回"按钮。 EXTERNAL_PROGRAM_EXE不执行任何缓冲。对不起您的误导,我放置了更多代码来说明我要问的问题

我真的不明白你的问题。是的,您可以阅读childs stdout并将其附加到TextView。如果使用readline()进行读取,直到换行符到达stdout之前,您将不会获得任何数据。如果您需要在写入数据后立即读取数据,请将BashProcessThread(p.stdout.readline)替换为BashProcessThread(p.stdout.read1)

我只想知道是否可以使用单独的线程来继续注销wx.TextCtrl上的子进程stdout。我尝试了上面的代码(PrintThread)。这是行不通的。根本没有输出。

没有输出或没有向TextCtrl添加任何内容?尝试printf。可能是WX只是不允许添加数据以从任意线程进行控制。

嗨,大火,好吧,到目前为止,TextCtrl上有一些输出,但是在某些消息或其他输入之后不久它就会崩溃。我修改了我的原始帖子,并提供了更多详细信息。非常感谢您的跟进!

最大的问题是您不能强制子进程不缓冲其输出,并且大多数程序的标准I / O库将在stdout是管道时缓冲输出(更准确地说,它们将从行缓冲变为块缓冲)。诸如Expect之类的工具通过在伪tty中运行子流程来解决此问题,这实际上会欺骗子流程,使其认为其输出将到达终端。

有一个称为Pexpect的Python模块,它以与Expect相同的方式解决了这个问题。我从未使用过它,所以请警告。

嗨,Marcelo,谢谢您的输入。缓冲来自我的线程,而不是后台子进程程序。后台程序未缓冲其输出消息。即使stdout管道成为了块缓冲区,对于我的应用程序也没关系。我只需要一种伪时地打印交易消息的方法,或者至少不需要等待下一个输入/"返回"按钮。此外,就像我为GUI所做的那样,我使用wx.textctrl与用户进行交互式输入/输出。我可以为此使用pty / pexpect吗?您是否认为可以使用一个线程(继续打印到wx.TextCtrl)来实现这一目标?

嗨,马塞洛,谢谢您的投入。更正了我的错误(从辅助线程调用GUI)后,我发现问题出在您所说的问题上,为了进一步了解,我创建了另一篇文章。最后,为答谢您详尽而有见地的答案(我也非常感谢),我将您的答案接受。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值