2

这是一个例子:

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 运行时冻结,所以我将其调整为:

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 的正确方法吗?

4

2 回答 2

1

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

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

可以在wxPython wiki(第一个示例)中找到如何更新具有长时间运行任务的 GUI 的最佳说明。

于 2013-08-16T14:30:27.060 回答
1

找了wxpython玩了一阵子,终于找到了解决办法,其实很简单,刷新一下面板就OK了(在ChangeBG方法中加入这行)self.panel.refresh():。我不知道为什么存在故障。

至于 Rostyslav 的回答,非常感谢队友!

“从工作线程更新 GUI 是一种不好的方法:”,我认为你的意思是,就线程安全问题而言,直接将 GUI 代码插入工作线程(这正是我在第一个示例中所做的)是不礼貌的,基本上是那些GUI 代码应该被包装到线程安全的方法中(这正是我在第二个示例中尝试做的),然后排队进入 GUI 主线程。

我发现对于工作线程中的 GUI 更新基本上有三种线程安全的方法:和wx.PostEvent,但我从来不喜欢,这有点麻烦,你也必须想出自己的事件,这就是为什么是一个更好的选择我,它更pythonic且易于使用,实际上就像一个高级包装器,如果您查看以下源代码:wx.CallAfterwx.CallLaterwx.PostEventwx.CallAfterwx.CallAfterwx.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

于 2013-08-16T15:32:22.870 回答