wxPython app hanging, not ending MainLoop

513 views
Skip to first unread message

Johannes Brunen

unread,
May 25, 2016, 9:56:34 AM5/25/16
to wxpytho...@googlegroups.com

Hello,

 

I have a problem with my wxPython based application. The application hangs and does not leave the MainLoop.

 

Let me first describe my setup. I have a windows MFC c++ application that calls a python program via the COM interface. This python program is a win32 COM server. This program does have a need for a GUI frontend based on wxPython. In order to separate this GUI from my main c++/Python application I use the python multiprocessing.Process class to create a separate python process in which I run my wx.App based application. Below you can find my simplified code.

 

If I use 'python.exe' instead of 'pythonw.exe' in the multiprocessing.set_executable  statement, the wx.App based application terminates fine, i.e. its OnExit handler is called and the MainLoop finishes as expected. However, if I use 'pythonw.exe' instead, this is not the case anymore. Neither OnExit is called nor is the MainLoop finished. However, the frame OnCloseFrame handler is still called. I did try to use Destroy() instead of Skip() but that doesn't work either.

 

So my question is, do anyone has an explanation why the gui is hanging when I use 'pythonw.exe'. Additionally, do you know a measure with which I can solve my issue.

 

Best,

Johannes

 

# GuiProcess runs function creation_fun in its own process. Here it is function StartNumber

class GuiProcess(multiprocessing.Process):

    def __init__(self, inQueue, outQueue, creation_fun, process_name = 'GuiProcess', *args, **kwargs):

        self._app           = None

        self._inQueue       = inQueue

        self._outQueue      = outQueue

        self._creation_fun  = creation_fun

 

        multiprocessing.set_executable(os.path.join(dirPython,'pythonw.exe'))

        multiprocessing.Process.__init__(self, name = process_name)

 

        self._args   = tuple(args)

        self._kwargs = dict(kwargs)

        self.daemon  = True

 

    def run(self):

       self._app = self._creation_fun(app_data, self._inQueue, self._outQueue, *self._args, **self._kwargs)

 

 

# GuiApp uses helper GuiProcess to call function creation_fun in a new process

class GuiApp():

    def __init__(self, data, creation_fun, callbacks, *args, **kwargs):

        self.data = data

        self.callbacks = callbacks

        self.inQueue  = multiprocessing.Queue() # push data into gui process

        self.outQueue = multiprocessing.Queue() # receive data from gui process

 

        self.gui_process = GuiProcess(self.inQueue, self.outQueue, creation_fun, 'GuiProcess', *args, **kwargs)

        self.gui_process.start()

 

 

class StartNumberFrame(wx.Frame):

    def __init__( self, msgDict, data, inQueue, outQueue, *args, **kwargs ):

        self.Bind( wx.EVT_CLOSE, self.OnCloseFrame )

        ...

 

    def OnCloseFrame( self, event ):

        ...

        event.Skip()

 

 

class StartNumberApp(wx.App):

    def __init__(self, msgDict, data, inQueue, outQueue):

        wx.App.__init__(self, False)

 

   def OnInit(self):

        parent = GetCADdyParent()

       

        frame = StartNumber.StartNumberFrame(self.msgDict, self.data, self.inQueue, self.outQueue, parent)

        frame.Show(True)

        self.SetTopWindow(frame)

 

       return True

       

    def OnExit(self):

        ...

 

 

# This function creates a wx.App based instance and starts its main loop.

def StartNumber(data, inQueue, outQueue, *args, **kwargs):

   app = StartNumberApp(msgDict, data, inQueue, outQueue)

    app.MainLoop()

 

 

# The main python application started from my MFC windows C++ program via COM

class ActRenumber(ActionBase):

    def __init__(self, ...):

        self.app_start_number = GuiApp(...,StartNumber, ...)     # GuiApp takes function StartNumber which is finally responsible for the creation of the wxApp application run in a separate process.

 

 

 

 

 

Johannes

unread,
May 30, 2016, 5:58:20 AM5/30/16
to wxpytho...@googlegroups.com
Hi,

I would like to give some additional informations. Maybe someone does
have an idea with these :-)

1. I do have the problem with wxPython 3.0.2 on windows 64 bit, i.e. the
pythonw.exe is not terminated after closing the frame of my wx.App based
python application. I do have to manually remove it from the windows
task manager.

2. As already noted, if I do use python.exe instead of pythonw.exe I do
not see any problem, i.e the main loop of the wx.App program is
terminated correctly.

3. The problem does not show up with version wxPython 3.0.0.

4. I use the following code do get the parent for my wx.Frame from the
foreign MFC application:

def GetCADdyParent():
parent = None

from CADdy.Application import CheckApplic
if CheckApplic():
try:
from CADdy.Application import GetApplic
applic = GetApplic()
uiManager = applic.UIManager()

# Get the native window handle from the main non wx
# application.
w = uiManager.MainWindowHandle

parent = wx.Window_FromHWND(None, w)

except:
pass

return parent

5. If I do not call the GetCADdyParent function, i.e. if I do initialize
my frame with no parent the wx.App OnExit() handler is called and the
main loop is terminated correctly.

6. The following style flags are used on initialization of my frame:
wx.DEFAULT_FRAME_STYLE
wx.FRAME_FLOAT_ON_PARENT
wx.FRAME_NO_TASKBAR
wx.RESIZE_BORDER
wx.SYSTEM_MENU
wx.TAB_TRAVERSAL

7. It does not make any difference with respect of leaving the main
loop, whether I use the following two style flags:

wx.FRAME_FLOAT_ON_PARENT
wx.FRAME_NO_TASKBAR


What could be the reason for the not ending wx.App main loop?
What is the exact termination process?
What I mean is, what is the function call order that finally leads to
wx.Frame.OnExit() and terminates the wx.App.mainLoop()?
What could prohibit this termination process?
What are the C++ sources that I have to look into in order to get some
insight into the termination process?

I would really appreciate some help with this problem.

Best,
Johannes






Dev Player

unread,
May 31, 2016, 12:35:03 PM5/31/16
to wxpython-users
Some reasons a wxPython application may not fully exit properly is:

. not all top level windows are destroyed

. not all system resources are released such as stdio, timers, gdc's, network stuff, etc.

. not all threads are properly closed or they are in a deadlock

. or you are processing wxPython generated events outside of the same process as the wxApp wxPython process  - often the wxPython process expects to be the "main" or top-most process.


Tim Roberts

unread,
May 31, 2016, 2:14:03 PM5/31/16
to wxpytho...@googlegroups.com
Johannes wrote:
4. I use the following code do get the parent for my wx.Frame from the 
foreign MFC application:

Did you tell us previously that the parent window for your frame is a window handle from a different process?  That's a very unusual thing to do.  The net result of this is to link the two message queues together in ways that wxPython is almost certainly not prepared to handle.  In particular, the window destruction chain is known to be delicate in this case.  If you get a clue when the app is going to terminate, the suggestion is that you break the parent/child relationship before destroying the windows.  That separates the message queues again.

Are you running your own main loop, or are you relying on the MFC application to do the dispatching for you?
-- 
Tim Roberts, ti...@probo.com
Providenza & Boekelheide, Inc.

Johannes

unread,
Jun 1, 2016, 8:49:42 AM6/1/16
to wxpytho...@googlegroups.com
Hello Tim,

> Did you tell us previously that the parent window for
> your frame is a window handle from a different process?

I use wx.Window_FromHWND(None, hwnd_of_foreign_app_window) as the parent
of my wx.Frame.
What I would like to archieve here is, that the wxPython application,
running in its own process, stay on top of my main MFC application.

> That's a very unusual thing to do.

How else can you bring your wxPython application always staying on top
of another window?

> The net result of this is to link the two message queues
> together in ways that wxPython is almost certainly not
> prepared to handle.

Hmm, but it works great with the exception of the mainLoop termination
and that only if I use pythonw.exe instead of python.exe. Also if I use
wxPython 3.0.0 I do not see this problem.

Below, I have condensed my setup so that you can see what I have done.

self.gui_process = GuiProcess(..., StartNumber, ...)
self.gui_process.start()

def StartNumber(...):
app = StartNumberApp(...)
app.MainLoop()

class GuiProcess(multiprocessing.Process):
# creation_fun is def StartNumber
def __init__(self, ..., creation_fun, ...):
self._creation_fun = creation_fun

# with python.exe instead, everything works correctly!
multiprocessing.set_executable('pythonw.exe')
multiprocessing.Process.__init__(self, ...)

self.daemon = True

def run(self):
self._app = self._creation_fun(...)

class StartNumberFrame(wx.Frame):
def __init__( self, parent, ... ):
wx.Frame.__init__ ( self, parent, ..., style =
wx.DEFAULT_FRAME_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.FRAME_NO_TASKBAR )
self.Bind( wx.EVT_CLOSE, self.OnCloseFrame )
...
def OnCloseFrame( self, event ):
...
event.Skip()

class StartNumberApp(wx.App):
def __init__(self, ...):
wx.App.__init__(self, False)

def OnInit(self):
parent = GetCADdyParent()

frame = StartNumber.StartNumberFrame(..., parent)
frame.Show(True)

self.SetTopWindow(frame)

return True

def OnExit(self):
# After closing the frame OnExit is
# not called if used pythonw.exe in
multiprocessing.set_executable,
# but called if used python.exe instead
...

def GetCADdyParent():
parent = None

from CADdy.Application import CheckApplic
if CheckApplic():
try:
from CADdy.Application import GetApplic
applic = GetApplic()
uiManager = applic.UIManager()

# Get the window handle (HWND) of the main window
# of the MFC based C++ application.
hwnd = uiManager.MainWindowHandle
parent = wx.Window_FromHWND(None, hwnd)
except:
pass

return parent


Alternatively, I have tried the following implementation path in oder to
use DissociateHandle() on my frame after creation. But it does not solve
the termination problem. Otherwise it works like my original solution.

class StartNumberApp(wx.App):
def OnInit(self):
...
frame = StartNumber.StartNumberFrame.create(self.msgDict,
self.data, self.inQueue, self.outQueue, None)
return True

class ForeignFrame(wx.Frame):
_top_hwnd = None

def __init__(self, parent, id=-1, title="", pos=(-1, -1), size=(-1,
-1), name='frame', resize=True,
style=wx.DEFAULT_FRAME_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.FRAME_NO_TASKBAR):
wx.Frame.__init__(self, parent, id, title, pos, size, style)

self.panel = None

self.initialPosOffset = pos
self._runningModal = False

if (not resize):
wx.Frame.ToggleWindowStyle(self, wx.RESIZE_BORDER)

self.Bind(wx.EVT_CLOSE, self.on_close)

@classmethod
def create(cls, *args, **kwargs):
app = wx.GetApp()

if (app == None):
app = wx.App(redirect=False)

topHandle = ForeignFrame._get_top_hwnd()

preFrame = wx.PreFrame()
preFrame.AssociateHandle(topHandle)
preFrame.PostCreate(preFrame)

app.SetTopWindow(preFrame)

try:
frame = cls(preFrame, *args, **kwargs)
frame.Show(True)
except:
frame = None

preFrame.DissociateHandle()

return frame

@staticmethod
def _get_top_hwnd():
if ForeignFrame._top_hwnd is not None:
return ForeignFrame._top_hwnd

hwnd = None

from CADdy.Application import CheckApplic
if CheckApplic():
try:
from CADdy.Application import GetApplic
applic = GetApplic()
uiManager = applic.UIManager()
hwnd = uiManager.MainWindowHandle
except:
pass

return hwnd


def on_close(self, evt):
self.Show(False)
self.Destroy()

> If you get a clue when the app is going to terminate,
> the suggestion is that you break the parent/child
> relationship before destroying the windows.
> That separates the message queues again.

Ok, that is a good hint and it solved the termination problem. I added a
parent.RemoveChild(self) call to the OnCloseFrame handler just before
the call of event.Skip().

class StartNumberFrame(wx.Frame):
def __init__( self, parent, ... ):
wx.Frame.__init__ ( self, parent, ..., style =
wx.DEFAULT_FRAME_STYLE|wx.FRAME_FLOAT_ON_PARENT|wx.FRAME_NO_TASKBAR )
self.Bind( wx.EVT_CLOSE, self.OnCloseFrame )
...
def OnCloseFrame( self, event ):
...
parent = self.GetParent()
if parent is not None:
parent.RemoveChild(self)
event.Skip()

Thank you for taking your time.

Best,
Johannes


Reply all
Reply to author
Forward
0 new messages