在wxPython/python中,wx.CallAfter()导致应用崩溃的无限循环

1 投票
2 回答
1233 浏览
提问于 2025-04-18 00:18

我正在使用Python 2.7和wxPython 3.0,在Windows 7操作系统上工作。在下面的代码片段中,我实现了一个发布-订阅机制,用来更新我的图形用户界面(GUI)。我使用了wx.CallAfter()来把更新发送到我的GUI主循环。

问题:为什么我提供的代码片段会导致我的应用程序崩溃?这是为什么呢?我该如何避免这种情况?

代码:代码中使用的图片可以从这里下载 blue.bmpg.bmp

import wx
import time
from wx.lib.pubsub import setupkwargs
from wx.lib.pubsub import pub
from threading import Thread
import threading
import random

class gui(wx.Frame):

    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, None, id, title, size=(200,200), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
        self.mainPanel = mainPanel = wx.Panel(self, -1)
        mySizer = wx.BoxSizer(wx.VERTICAL)
        self.myButton1 = wx.Button(mainPanel, -1, size=(30,30))
        mySizer.Add(self.myButton1)
        mainPanel.SetSizer(mySizer)
        Bimage_file = 'blue.bmp'
        self.Blue  = wx.Image(Bimage_file, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
        Gimage_file = 'g.bmp'
        self.Green  = wx.Image(Gimage_file, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
        pub.subscribe(self.updateImages, 'Update')

    def updateImages(self, value):
        self.myButton1.DestroyChildren()
        if value <= 5:
            wx.StaticBitmap(self.myButton1, -1, self.Blue, (0, 0))
            self.mainPanel.Layout()
        else:
            wx.StaticBitmap(self.myButton1, -1, self.Green, (0, 0))
            self.mainPanel.Layout()
class myThread(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.start()
    def run(self):
        while True:
            result = random.randrange(0,10)
            wx.CallAfter(pub.sendMessage, 'Update', value=result)
            #Uncomment the following line to avoid the crash!!!
            #time.sleep(1)
if __name__=='__main__':
    app = wx.App()
    frame = gui(parent=None, id=-1, title="Demo")
    frame.Show()
    myThread()
    app.MainLoop()

不过,如果我在wx.CallAfter()之后添加以下这一行,程序就能正常工作:

time.sleep(1)

感谢你的时间!

2 个回答

0

也许你的线程应该有一个返回的回调函数,或者用一个变量(信号量)来知道 CallAfter 是否已经执行完毕?

把你的 pub.sendMessage 调用放在另一个函数里,这个函数可以在发送消息后清除一个信号量标志。你的线程可以在发出 CallAfter 之前设置信号量,然后在发送另一个消息之前等待信号量被清除。

如果你把它做成一个计数器的话,可以在设置时加一,在清除时减一,并且只允许计数器到达一定的上限,这样就能限制排队的 CallAfter 的数量。

3

这个问题可能是因为Windows的消息队列溢出了。关于这个bug,在wxWidgets的bug跟踪系统里有好几个相关的记录,其中一些问题已经修复了,但可能还不是全部。

无论如何,即使我们能总是避免溢出,像这样不断向主线程发送事件也是个坏主意(因为每次调用wx.CallAfter()实际上就相当于发送了一个事件)。在实际情况下,后台线程会在这些事件之间做一些工作,所以这种情况不太可能发生,但如果你真的需要在这样的简单示例中让它工作,使用sleep()可能是个不错的选择。

撰写回答