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

标签 python multithreading wxpython

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

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

(python:12728): Gtk-CRITICAL **: IA__gtk_window_set_modal: 断言 'GTK_IS_WINDOW (window)' 失败

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

我在这里尝试用精简版模拟它:

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

(我们选择 2.8,但理想情况下,任何修复都应该在 2.8 和 3.0 中都有效。实际上,由于 3.0 构建错误,我无法在 linux 3.0 中对其进行测试)

这很好地说明了问题:在 Windows 中工作正常,但当它试图破坏进度对话框时出现段错误。但是,我无法获得显示 GTK_IS_WINDOW

的示例

我尝试寻找解决方案。我读到这可能是由于工作线程完成得太快,因此在 GUI 的队列中留下了一些消息。我不确定我是否完全理解这一点(从来没有掌握产量和消息等),但我认为这意味着当工作人员处于 100% 时,ProgressDialog(变慢)可能只会在75%,还有额外的 25% 的消息可用于“更新”GUI,但会被销毁。

我想澄清一下我的理解是否正确。

此外,我相信 .Hide() 可以解决问题,但我想改为销毁它,因为这是正确的做法。

无论如何,我们将不胜感激。 =)

最佳答案

我试过你的代码,也尝试过许多修改来解决这个问题,但都失败了。无论如何,我已经创建了以下 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()

我正在使用 wx.Gaugewx.ProgressDialog 做的事情,还有一个额外的 wx.Timer 来显示耗时。 MakeModal() 方法用于模仿 ShowModal 效果,这是 Dialog 显示的默认样式,不要忘记释放模态状态MakeModal(False) 否则框架将被卡住。您可以在 ProgressBarFrame 类中添加更多内容。

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

Screenshot of progressbar demo

关于python - wx.ProgressDialog 在被销毁时导致段错误和/或 GTK_IS_WINDOW 失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30959844/

相关文章:

python - 将数据拟合到广义极值分布

c# - 'thread safe' 对象是什么意思?

java - 如何在具有相同主体的线程中启动新线程?

python - wxPython 的 "Inception"情况

python - 如何在不透明区域内绘制透明部分

python - wxPython auinotebook.GetSelection() 返回第一页的索引

python - 如何将绘图保存为 pdf 文件,但它必须看起来像 plt.show() 的结果?

python - Tkinter 在 Pane 窗口中动态调整小部件的大小

比异步更快的 Python 同步代码示例

java - 多线程 - 在线程池中安排永无休止的可运行