0

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

我尝试构建它的方式是使用相当简单的 Python 线程布局。单击“开始监控”按钮时,程序会生成一个线程,用实时信息更新标签。单击“停止监控”按钮时,将调用 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 线程还很陌生,这是我想出的第一件事。

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

4

3 回答 3

2

在 wxPython 中,GUI 操作需要在主线程中进行。在代码中的某些地方,您正在从不同的线程调用 GUI。

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

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

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

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

其他问题也很相关,例如您无法重新启动同一个线程,但使用 CallAfter 将停止 crashing

于 2010-05-17T20:46:31.320 回答
1

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

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

我会加入一些调试print语句来看看发生了什么。

编辑:

我也会使用 athreading.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()

这不应该使它的工作方式有所不同,但它是一种更安全的方法。

于 2010-05-17T20:12:34.687 回答
1

tom10 有一个正确的想法,即避免从监视器线程更新 UI。

self.monThread.join()此外,在 UI 线程中进行阻塞调用可能不是一个好主意。如果您希望 UI 给出一些反馈,即监控线程实际上已经结束,请让 monThreadWorker 在它关闭之前发出 wx.CallAfter() 或 wx.PostEvent()。

避免任何在你的 UI 线程中阻塞的东西,你将避免 UI 死锁

于 2010-05-17T22:42:30.470 回答