按需Python线程启动/加入从wxPython GUI冻结

时间:2010-05-17 19:28:38

标签: python multithreading wxpython windows-7-x64

我正在尝试构建一个非常简单的wxPython GUI来监视和显示外部数据。有一个按钮可以打开/关闭监控。打开监视时,GUI会使用实时数据更新几个wx StaticLabel。当监控关闭时,GUI会闲置。

我尝试构建它的方式是使用相当简单的Python Thread布局。单击“开始监视”按钮时,程序会生成一个线程,该线程使用实时信息更新标签。单击“停止监视”按钮时,将调用thread.join(),它应该停止。

启动功能正常工作,实时数据更新效果很好,但是当我点击“停止”时,整个程序会冻结。我在Windows 7 64位上运行它,所以我得到了通常的“此程序已停止响应”Windows对话框。

以下是相关代码:

class MonGUI(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        ...
        ... other code for the GUI here ...
        ...
        # Create the thread that will update the VFO information
        self.monThread = Thread(None, target=self.monThreadWork)
        self.monThread.daemon = True
        self.runThread = False

    def monThreadWork(self):
        while self.runThread:
            ...
            ... Update the StaticLabels with info
            ... (This part working)
            ...

    # Turn monitoring on/off when the button is pressed.
    def OnClick(self, event):
        if self.isMonitoring:
            self.button.SetLabel("Start Monitoring")
            self.isMonitoring = False
            self.runThread = False
            self.monThread.join()
        else:
            self.button.SetLabel("Stop Monitoring")
            self.isMonitoring = True

            # Start the monitor thread!
            self.runThread = True
            self.monThread.start()

我确信有更好的方法可以做到这一点,但我是GUI编程和Python线程的新手,这是我想出的第一件事。

那么,为什么单击按钮停止线程会使整个事情冻结?

3 个答案:

答案 0 :(得分:2)

在wxPython中, GUI操作需要在主线程中进行。在代码中的某些位置,您从另一个线程调用GUI。

最简单的解决方案是使用wx.CallAfter()。一行代码看起来像

wx.CallAfter(self.button.SetLabel, “Start Monitoring”)

然后在函数完成后从主线程调用self.button.SetLabel(“Start Monitoring”)。

还有其他方法可以解决这个问题,例如使用Python线程队列或wx.PostEvent,但是从CallAfter开始,因为它最简单。

其他问题也很重要,比如你无法重启同一个帖子,但使用CallAfter会停止崩溃

答案 1 :(得分:1)

它可能挂在join([timeout])上,它会阻塞调用线程,直到调用join()方法的线程终止 - 正常或通过未处理的异常 - 或直到可选的超时发生。

你的线程中有一些内部循环,还是等待某些可能永远不会出现的数据源的阻塞调用?当我编写一个抓取COM端口数据的基本串行程序时,它有时会挂起,因为我的线程中的读取函数会阻塞,直到它有所作为。

我会在一些调试print语句中查看最新情况。

修改

我还使用threading.Event()而不是布尔标志,例如:

# in the init code...
self.runThread = threading.Event()

# when starting thread...
self.runThread.set()
self.monThread.start()

# in the thread...
while self.runThread.isSet():
    pass # do stuff

# killing the thread...
self.runThread.clear()
self.monThread.join()

这不应该使它以不同的方式工作,但它是一种稍微更安全的方式。

答案 2 :(得分:1)

tom10有正确的想法,可以避免来自监视器线程的UI更新。

此外,在UI线程中使用阻止调用self.monThread.join()可能不是一个好主意。如果您希望UI提供监视器线程实际结束的一些反馈,请让monThreadWorker在关闭之前发出wx.CallAfter()或wx.PostEvent()。

避免在您的UI线程中阻塞任何内容,并且您将避免死锁UI