wx.ProgressDialog在被销毁时导致seg错误和/或GTK_IS_WINDOW失败

bli*_*ann 6 python multithreading wxpython

这只发生在Linux上(也可能是OS X,无法测试atm),在Windows上运行正常.

我有一个wx.ProgressDialog与主线程产生.我将工作发送到另一个线程,它会定期回调主线程中的回调函数,该函数将更新ProgressDialog,或者在工作结束时将其销毁.但是,当发生这种情况时,我在Linux上收到一条有趣的消息:

(python:12728): Gtk-CRITICAL **: IA__gtk_window_set_modal: assertion 'GTK_IS_WINDOW (window)' failed

该对话框确实关闭,但如果我再次尝试生成它,它看起来已经差不多完成了.有时,seg错误也会跟随此消息.

我试图在这里使用精简版本来模拟它:

import wxversion
wxversion.select("2.8")
import wx
import sys
import threading

MAX_COUNT = 100

## This class is in a different area of the codebase and
class WorkerThread(threading.Thread):
    def __init__(self, callback):
        threading.Thread.__init__(self)
        self.callback = callback

    def run(self):
        # simulate work done. IRL, this calls another function in another
        # area of the codebase. This function would generate an XML document,
        # which loops through a list of items and creates a set of elements for
        # each item, calling back after each item. Here, we simply set up a for
        # loop and simulate work with wx.MilliSleep
        for i in xrange(MAX_COUNT):
            print i
            wx.MilliSleep(30)
            wx.CallAfter(self.callback, i)

        # Send done signal to GUI
        wx.CallAfter(self.callback, -1)

class Frame(wx.Frame):
    def __init__(self, title):
        wx.Frame.__init__(self, None, title=title, pos=(150,150), size=(350,200))

        panel = wx.Panel(self)
        box = wx.BoxSizer(wx.VERTICAL)

        m_btn = wx.Button(panel, wx.ID_ANY, "Run Stuff")
        m_btn.Bind(wx.EVT_BUTTON, self.OnRunButton)
        box.Add(m_btn, 0, wx.ALL, 10)

        panel.SetSizer(box)
        panel.Layout()

    def OnRunButton(self, event):
        self.progressDialog = wx.ProgressDialog("Doing work",
                          "Doing Work",
                          maximum=MAX_COUNT, parent=self,
                          style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME)
        self.worker(self.threadCallback)
        self.progressDialog.ShowModal()

    def worker(self, callback):
        # This bit is in another part of the codebase originally. In the test,
        # I could have added it to OnRunButton, but I wanted function calls to
        # be similar between test and actual code
        thread = WorkerThread(callback)
        thread.start()

    def threadCallback(self, info):
        # We update based on position, or destroy if we get a -1
        if info == -1:
            self.progressDialog.Destroy()
        else:
            self.progressDialog.Update(info)

app = wx.App(redirect=False)
top = Frame("ProgressDialog Test")
top.Show()
app.MainLoop()
Run Code Online (Sandbox Code Playgroud)

(我们选择2.8,但理想情况下,任何修复都应该在2.8和3.0中都有效.由于3.0版本的错误,我实际上无法在linux中测试3.0)

这在表示问题方面做得很好:在Windows中工作正常,但在尝试销毁进度对话框时出现seg错误.但是,我无法通过示例来展示GTK_IS_WINDOW

我试过寻找解决方案.我已经读过,这可能是因为工作线程完成得太快,因此在GUI中留下了一些消息.我不确定我是否完全理解这一点(从未得到过收益和消息等等),但我认为这意味着当工人处于100%时,ProgressDialog(变慢)可能只是在75%,并且还有额外25%的消息用于"更新"GUI,而是被销毁.

如果我理解正确与否,我想澄清一下.

另外,我认为.Hide()可以解决这个问题,但是我想要去除它,因为这是正确的做法.

无论如何,任何帮助将不胜感激.=)

Ton*_*ony 1

我已经尝试过你的代码,也尝试了许多修改来解决这个问题,但失败了。无论如何,我创建了以下 wxPython 脚本来实现您的目的,请参见下文:

import wxversion
wxversion.select("2.8") # version 3.0 works, too.
import wx
import sys
import threading
import time

MAX_COUNT = 200

class WorkerThread(threading.Thread):
    def __init__(self, target, countNum):
        threading.Thread.__init__(self, target = target)
        self.setDaemon(True)
        self.cnt = countNum
        self.target = target
        self.pb = self.target.pb

    def run(self):
        for i in xrange(self.cnt):
            print i+1
            wx.MilliSleep(50)
            wx.CallAfter(self.pb.SetValue, i+1)

        wx.CallAfter(self.target.MakeModal, False)
        wx.CallAfter(self.target.Close)

class ProgressBarFrame(wx.Frame):
    def __init__(self, parent, title, range = 100) :
        wx.Frame.__init__(self, parent = parent, title = title)
        self.range = range
        self.createProgressbar()
        self.SetMinSize((400, 10))
        self.Centre()
        self.Show()
        self.t0 = time.time()
        self.elapsed_time_timer.Start(1000)

    def createProgressbar(self):
        self.pb       = wx.Gauge(self)
        self.pb.SetRange(range = self.range)

        self.elapsed_time_st  = wx.StaticText(self, label = 'Elapsed Time:')
        self.elapsed_time_val = wx.StaticText(self, label = '00:00:00')

        vbox_main = wx.BoxSizer(wx.VERTICAL)
        hbox_time = wx.BoxSizer(wx.HORIZONTAL)
        hbox_time.Add(self.elapsed_time_st,  0, wx.ALIGN_LEFT | wx.EXPAND | wx.ALL, 5)
        hbox_time.Add(self.elapsed_time_val, 0, wx.ALIGN_LEFT | wx.EXPAND | wx.ALL, 5)
        vbox_main.Add(self.pb,   0, wx.EXPAND | wx.ALL, 5)
        vbox_main.Add(hbox_time, 0, wx.EXPAND | wx.ALL, 5)

        self.SetSizerAndFit(vbox_main)

        self.elapsed_time_timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.onTickTimer, self.elapsed_time_timer)

    def onTickTimer(self, event):
        fmt='%H:%M:%S'
        self.elapsed_time_val.SetLabel(time.strftime(fmt, time.gmtime(time.time()-self.t0)))

class Frame(wx.Frame):
    def __init__(self, title):
        wx.Frame.__init__(self, None, title=title, pos=(150,150), size=(350,200))

        panel = wx.Panel(self)
        box = wx.BoxSizer(wx.VERTICAL)

        m_btn = wx.Button(panel, wx.ID_ANY, "Run Stuff")
        self.Bind(wx.EVT_BUTTON, self.OnRunButton, m_btn)
        box.Add(m_btn, 0, wx.ALL, 10)

        panel.SetSizer(box)

    def OnRunButton(self, event):
        self.progressbar = ProgressBarFrame(self, 'Working Processing', MAX_COUNT)
        self.progressbar.MakeModal(True)
        worker = WorkerThread(self.progressbar, MAX_COUNT)
        worker.start()

app = wx.App(redirect=False)
top = Frame("ProgressDialog Test")
top.Show()
app.MainLoop()
Run Code Online (Sandbox Code Playgroud)

我用来wx.Gauge做的wx.ProgressDialog事情,以及额外的wx.Timer显示经过的时间。MakeModal()方法用于模仿ShowModal默认样式Dialog显示的效果,不要忘记释放模态状态,MakeModal(False)否则框架将被冻结。您可以在课堂上添加更多内容ProgressBarFrame

我认为段错误可能是由事件调用引起的,特别是当multithreading涉及问题时,也许仔细检查类wx.ProgressDialog会显示一些线索。

进度条演示截图