正确使用 wxPython 的线程

using threads with wxPython correctly

我正在使用 wxPython 构建程序的 GUI,我在使用 运行 函数时遇到了问题,该函数使用线程从文件中获取数据并将其显示在 ObjectListView 中,读取数据从文件运行正常,但显示来自线程的数据有时会使整个程序崩溃。 gui.ProgressDialog 是一个 wx.Frame,它将显示进度条,直到线程完成。 self.dv 是一个 ObjectListView,一个派生自 wx.ListCtrl 的对象,set_data 是我编写的一个函数,它遍历数据并将其添加到列表中。

def set_data(self,data):
    """set the data on the ObjectListView"""
    pd = gui.ProgressDialog("Setting Data..")
    def set_thread():
        self.dv.set_data(data) #Takes time
        pd.Close()

    #opens a thread that sets the data
    t = Thread(target = set_thread,name="SetDataThread")
    t.start()

我已经阅读了一些关于 wx.CallAfter 的内容,我应该使用它,但我不明白如何使用。我试过这样调用 set_data - wx.CallAfter(self.dv.set_data,data) 但也没用。有人可以解释函数 wx.CallAfterwx.CallLaterwx.PostEvent ,它们在将线程与 wxPython 一起使用时有何帮助以及我应该使用其中的哪一个?或者其他解决方案?

您不能从辅助线程进行任何 GUI 调用。这在 wx.Thread 文档中有解释,但它也适用于正常的 Python threading.Thread 线程:

GUI calls, such as those to a wxWindow or wxBitmap are explicitly not safe at all in secondary threads and could end your application prematurely.

因此,您要做的是向主线程发送一条消息,要求它为您进行 GUI 调用。

PostEvent 是低级解决方案。您创建了一种新的事件。您在主线程上为该事件类型编写一个处理程序,该处理程序根据事件中的信息执行一些 GUI 工作。然后在您的后台线程上,您使用适当的信息构造其中一个事件,然后 PostEvent 将其放入主线程的消息队列中。

其他选项基本上是围绕它的更高级别的包装器。 wxPyWiki 上的 CallAfter is probably the one you want. The CallAfter 页面有很好的解释和完整的示例,文档页面本身也有一个很好的示例,所以我不会尝试与他们竞争;相反,我将尝试解决您的具体问题。

我认为您缺少的是要点是将 GUI 调用推送到主线程。所有 GUI 调用,没有其他调用。

我不知道 self.dv 是什么,但它看起来不像 wx GUI 对象(它有一个 Python 风格的 set_data 方法,而不是 wx 风格 SetData)。当然,如果它在多个线程之间共享,并且不是自同步的,您几乎肯定需要 Lock 或其他同步对象来保护它。但是你不需要,也不想要,CallAfter 在这里。

另一方面,pd.Close 是一个 GUI 调用。 (好吧,如果你记得括号的话。)你肯定需要 CallAfter

所以:

def set_data(self,data):
    """set the data on the ObjectListView"""
    pd = gui.ProgressDialog("Setting Data..")
    def set_thread():
        data = takes_time()
        with self.dv_lock:
            self.dv.set_data(data)
        wx.CallAfter(pd.Close)

    #opens a thread that sets the data
    t = Thread(target = set_thread,name="SetDataThread")
    t.start()

您还询问了 CallLater。当您希望某些代码在 X 毫秒后而不是尽快在主线程上 运行 时,您可以使用它。虽然您可以使用 CallLater(0, pd.Close) 代替 CallAfter(pd.Close),但没有充分的理由这样做。