从wxPython GUI中按需启动/连接Python线程冻结

0 投票
3 回答
3349 浏览
提问于 2025-04-15 22:49

我正在尝试制作一个非常简单的wxPython图形界面,用来监控和显示外部数据。界面上有一个按钮,可以开启或关闭监控。当监控开启时,界面会实时更新几个wx StaticLabel,显示最新的数据;而当监控关闭时,界面就会处于闲置状态。

我尝试用一种相对简单的Python线程布局来实现这个功能。当点击“开始监控”按钮时,程序会创建一个线程,实时更新标签上的信息。而当点击“停止监控”按钮时,会调用thread.join(),本来应该停止线程。

启动功能是正常的,实时数据更新也很顺利,但当我点击“停止”时,整个程序就卡住了。我是在64位的Windows 7上运行这个程序,所以会出现常见的“该程序已停止响应”的对话框。

以下是相关的代码:

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()

我相信还有更好的方法来实现这个功能,但我对图形界面编程和Python线程还比较陌生,这个是我想到的第一个办法。

那么,为什么点击停止线程的按钮会导致整个程序卡住呢?

3 个回答

1

tom10 提出了一个好主意,就是避免让监控线程直接更新用户界面。

另外,把 self.monThread.join() 这个阻塞调用放在用户界面线程里也不是个好主意。如果你想让用户界面知道监控线程已经结束,可以让 monThreadWorker 在关闭之前调用 wx.CallAfter() 或 wx.PostEvent()。

尽量避免在用户界面线程中做任何会导致阻塞的操作,这样就能避免用户界面卡死的问题。

2

在wxPython中,图形界面的操作必须在主线程中进行。这意味着在你的代码中,有些地方是从其他线程调用图形界面的。

最简单的解决办法是使用 wx.CallAfter()。一行代码看起来像这样:

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

这行代码会在函数完成后,从主线程调用 self.button.SetLabel(“开始监控”)。

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

还有其他相关的问题,比如你不能重新启动同一个线程,但使用CallAfter可以防止程序崩溃

1

可能是因为在调用了join([timeout])这个方法时,程序会阻塞,也就是说它会等着调用这个方法的线程结束后才能继续执行。这个结束可以是正常结束,也可以是因为出现了未处理的错误,或者等到你设定的超时时间到了。

你的线程里是否有一个内部循环,或者有某个阻塞的调用在等着某些数据,而这些数据可能永远也不会到来?我曾经写过一个简单的串口程序来获取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()

这样做不应该改变程序的工作方式,但这是一个稍微安全一点的方法。

撰写回答