Crash exiting MainLoop()

of course, this Bind will not help much if the owners of the timers are casually changed ! :rofl:
(in the example it’s a difference of closing tic or tac first, thanks to the OS)

import wx

class Frame(wx.Frame):
    def __init__(self, frm, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.frm = frm
        self.timer = wx.Timer()
        print(f'{self.Title} timer is {id(self.timer)}')
        self.timer.SetOwner(self)
        self.timer.Start(1000)
        self.Bind(wx.EVT_TIMER, self.OnTimer)
        self.Bind(wx.EVT_WINDOW_DESTROY,
                lambda _: self.timer.Destroy())
        self.Show()

    def OnTimer(self, evt):
        print(f'{evt.GetEventObject().Title} from timer {id(evt.GetTimer())}')
        if self.frm:
            self.timer.SetOwner(self.frm)
            print(f'timer owner of {self.Title} switched to {self.frm.Title}')

if __name__ == "__main__":
    app = wx.App()
    frm = Frame(None, None, title='tic')
    Frame(frm, None, title='tac')
    app.MainLoop()

My code would only crash about 1/4 times. Like it was a race condition to see what shutdown first. It was a giant pain in the ass to debug because seeing what crashes the gui didn’t really have any debug tools. I had to bisect through the git changes to find a timer was added that looked perfectly harmless. And then I had to change the timer up to one not associated with the gui.

This issue is still an issue. And it’s really hard to trackdown when it crops up.

Sometimes it takes a while to understand your intent, sorry :sweat_smile:… and yes, after the window is destroyed (at the time the EVT_WINDOW_DESTROY handler is called), the added objects remain (even after the mainloop is terminated). It’s not magic at all.

well, this wasn’t really meant for you :hot_face: (I like your example)
but as you may well see from the discussion timers aren’t intuitive as other wx objects and on top of that, of course, garbage collection gives all that a real downside boost (one man’s meat is another man’s poison) :pleading_face:

well, it sounds as though both loops running at once (in a single process, some threads from behind?) :joy:

well, is this magic or just wx art :rofl:

import wx

class Frame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.timer = wx.Timer(self)
        self.timer.Start(1000)
        self.Bind(wx.EVT_TIMER, self.OnTimer)

    def OnTimer(self, evt):
        if evt.Timer is self.timer:
            print(self.Title)

    def Destroy(self):
        try:
            ## self.timer.Stop() # Don't forget this. Otherwise ...
            print('toe')
        finally:
            return wx.Frame.Destroy(self)

# if __name__ == "__main__":
    # app = wx.App()
    # frm = Frame(None, title='tic')
    # frm2 = Frame(None, title='tac')
    # frm2.Show()
    # frm.Show()
    # app.MainLoop()

class WxApp(wx.App):
    def __init__(self):
        super().__init__()
    def FilterEvent(self, _):
        return self.Event_Skip
    def OnInit(self):
        Frame(None, title='tic').Show()
        Frame(None, title='tac').Show()
        self.MainLoop()
        return True

if __name__ == "__main__":
    WxApp()

It’s mysterious that it doesn’t crash. I don’t see the difference with the commented code. I wonder if it’s OS specific :roll_eyes:

EDIT: Noticed that it doesn’t crash even if I write:

## if __name__ == "__main__":
##     app = wx.App()
##     frm = Frame(None, title='tic')
##     frm2 = Frame(None, title='tac')
##     frm2.Show()
##     frm.Show()
##     app.MainLoop()
if __name__ == "__main__":
    app = wx.App()
    Frame(None, title='tic').Show()
    Frame(None, title='tac').Show()
    app.MainLoop()

on Windows 10 :wink:

well, it’s simply the additional references which don’t go well with the wind down of the MainLoop (if you have only a single window it works), but I think that can always be avoided

more interesting will be to handle an additional Python async loop :sweat_smile:

let’s get some async into play :roll_eyes:
(the 10secs of the scheduler are to see the shut down more clearly :wink:, should really be under a sec)

from threading import Thread, enumerate
import datetime
import asyncio
import wx

class Frame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        pnl = wx.Panel(self)
        vbox = wx.BoxSizer(wx.VERTICAL)
        btn = wx.Button(pnl, label='start / stop')
        vbox.Add(btn, 0, wx.LEFT|wx.TOP, 20)
        btn.Bind(wx.EVT_BUTTON, self.evt_button)
        self.dt =  wx.StaticText(pnl)
        vbox.Add(self.dt, 0, wx.EXPAND|wx.LEFT|wx.TOP, 20)
        pnl.SetSizer(vbox)
        self.timer = wx.Timer(self)
        self.timer.Start(1000)
        self.Bind(wx.EVT_TIMER, self.OnTimer)
        def destroy(_):
            self.async_run = False
            self.timer.Destroy()
            print(f'toe {enumerate()}')
        self.Bind(wx.EVT_WINDOW_DESTROY, destroy)
        self.Show()
        self.async_run = None

    def OnTimer(self, evt):
        if evt.Timer is self.timer:
            print(self.Title)

    def evt_button(self, evt):
        self.async_run = False if self.async_run else True
        if self.async_run:
            RunAsync(self.async_gui)
        else:
            self.dt.SetLabel('')

    def async_gui(self, now):           # bridges async thread & Gui !!!
        if self.async_run:
            self.dt.SetLabel(now)
        return self.async_run

class RunAsync(Thread):
    def __init__(self, evh):
        super().__init__()
        self.evh = evh
        self.start()

    def run(self):
        asyncio.run(self.main())

    async def async_task(self):         # receives the stop
        return self.evh(
            f'{datetime.datetime.now():%a %b %d  %Y %H:%M:%S}')

    async def main(self):                   # scheduler
        while await self.async_task():
            await asyncio.sleep(10)

if __name__ == "__main__":
    app = wx.App()
    Frame(None, title='tic')
    Frame(None, title='tac')
    app.MainLoop()

the TopLevelWindows list in wxWidgets keeps every thing tidy, so there is no need for a local reference here (like in Qt :wink:)

and when the app’s main loop has gone only your own naked Python objects dawdle about… :ghost:

import gc
import wx

class Frame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.timer = wx.Timer(self)
        self.timer.Start(1000)
        self.Bind(wx.EVT_TIMER, self.OnTimer)
        self.Bind(wx.EVT_WINDOW_DESTROY,
                lambda _: self.timer.Destroy())
        self.Show()

    def OnTimer(self, evt):
        if evt.Timer is self.timer:
            print(self.Title)

if __name__ == "__main__":
    app = wx.App()
    frm = Frame(None, title='tic')
    Frame(None, title='tac')
    print(wx.GetTopLevelWindows())
    print(gc.get_referrers(wx.GetTopLevelWindows()[0])[0])
    print(gc.get_referrers(wx.GetTopLevelWindows()[1])[0])
    app.MainLoop()
    print(wx.GetTopLevelWindows())
    print(frm)