带线程的wxpython帧初始化错误

时间:2013-08-16 10:56:22

标签: python thread-safety wxpython

以下是一个例子:

class DemoFrame(wx.Frame):
    def __init__(self, parent):
         wx.Frame.__init__(self, parent)
         self.panel = wx.Panel(self, -1)
         ...
         initialize other elements
         ...
         self.DoStuff()


    def DoStuff(self):
         self.panel.SetBackGroundColour(wx.Colour(240, 240, 240))
         ...
         do something
         ...

现在您知道这绝对不是初始化GUI的好例子,因为do something很可能会在GUI运行时冻结GUI,所以我将其调整为:

import threading

class DemoFrame(wx.Frame):
    def __init__(self, parent):
         wx.Frame.__init__(self, parent)
         self.panel = wx.Panel(self, -1)
         ...
         initialize other elements
         ...
         DoStuffThead = threading.Thread(target = self.DoStuff, ())
         DoStuffThead.start()


    def DoStuff(self):
         wx.CallAfter(self.ChangeBG, )

         ...
         do something
         ...

    def ChangeBG(self):
         self.panel.SetBackGroundColour(wx.Colour(240, 240, 240))

上面的代码应该与do something为空时的第一个代码完全相同,但令我惊讶的是我注意到在运行后面的代码时几乎没有背景绘制故障。

出了什么问题?这不是在线程中更新GUI的正确方法吗?

2 个答案:

答案 0 :(得分:1)

从工作线程更新GUI是一种糟糕的方法,而不是说它不是线程安全的事件。 您必须与主线程通信才能更新GUI。

实现所需结果的最佳方法是使用用户wx.PostEvent方法。您可以根据需要创建自定义事件,继承自wx.PyEvent,并且您最好继承threading.Thread以保持您希望在该线程类中作为实例变量进行通信的窗口。

有关如何更新具有长时间运行任务的GUI的最佳说明,请参见wxPython wiki(第一个示例)。

答案 1 :(得分:1)

在搜索和玩wxpython一段时间之后,我终于找到了一个解决方案,它实际上很简单,只需刷新面板,一切都会好的(将这一行添加到ChangeBG方法中): self.panel.refresh()。我不知道为什么会出现故障。

至于Rostyslav的回答,非常感谢你的配偶!

“从工作线程更新GUI是一个糟糕的方法:”,我认为你的意思是将GUI代码直接插入工作线程是很粗鲁的(这正是我在第一个例子中所做的)在线程安全问题方面,基本上那些GUI代码应该包装成线程安全的方法(这正是我在第二个例子中尝试做的)然后排队到GUI主线程。

我发现工作线程中的GUI更新基本上有三种线程安全方法:wx.PostEventwx.CallAfterwx.CallLater,但我从不喜欢wx.PostEvent,这有点麻烦,你也必须提出你自己的事件,这就是为什么wx.CallAfter对我来说是更好的选择,它更加pythonic和易于使用,实际上wx.CallAfter就像一个高水平如果您查看wx.PostEvent中的源代码:

,请_core.py包装
def CallAfter(callable, *args, **kw):
    """
    Call the specified function after the current and pending event
    handlers have been completed.  This is also good for making GUI
    method calls from non-GUI threads.  Any extra positional or
    keyword args are passed on to the callable when it is called.

    :see: `wx.CallLater`
    """
    app = wx.GetApp()
    assert app is not None, 'No wx.App created yet'

    if not hasattr(app, "_CallAfterId"):
        app._CallAfterId = wx.NewEventType()
        app.Connect(-1, -1, app._CallAfterId,
                    lambda event: event.callable(*event.args, **event.kw) )
    evt = wx.PyEvent()
    evt.SetEventType(app._CallAfterId)
    evt.callable = callable
    evt.args = args
    evt.kw = kw
    wx.PostEvent(app, evt)

好吧,我从来没有在我的应用中尝试过wx.PostEvent,但我相信它也能正常运行。

哦,我发现这篇文章非常有帮助:wxPython and Threads