user2682863
user2682863

Reputation: 3217

thread fails to start if wx.app.mainloop isn't called from the main module

I'm hoping someone can explain this behavior to me. If I import a module that starts a wxpython interface, threads are unable to start until after the app.MainLoop() ends. Simplest example:

simple_app.py

import wx
from threading import Thread

def test():
    from time import sleep
    while 1:
        print("thread still running")
        sleep(2)

app = wx.App() 
frame = wx.Frame(None, -1, 'simple.py')
frame.Show()
thread = Thread(target=test)
thread.setDaemon(True)
thread.start()
app.MainLoop()

main.py

import simple_app

If you run simple_app.py by itself it works fine, if you run main.py the thread never starts... Why? I have a feeling it has to do with the thread being unable to secure a lock.

Upvotes: 3

Views: 460

Answers (2)

Nizam Mohamed
Nizam Mohamed

Reputation: 9220

The second thread in simple_app.py is trying to import the time module while an import is already running, which leads to deadlock when simple_app is being imported from the main module. Because imports acquire interpreter's import lock for the current thread while importing a module. It has been documented.

Threads in the main module can import other modules that's why running simple_app.py as main module works. Moving from time import sleep to module level in simple_app.py solves the problem.

Running the following code helps better understand the problem;

my_time.py
from time import sleep

simple_app.py

import imp
import sys
from threading import Thread
from time import sleep

import wx

class MyFinder(object):
    def __init__(self):
        print('MyFinder initializing')
        if imp.lock_held():
            while imp.lock_held():
                print('import lock held')
                sleep(2)
            print('import lock released')
        else:
            print('import lock is not held')

    def find_module(self, module, package=None):
        print('MyFinder.find_module called with module name "{}", pakcage name "{}"'.format(module, package))
        return None


def test():
    sys.meta_path.append(MyFinder())
    from my_time import sleep
    count = 0
    while True:
        print("{} thread still running".format(count))
        count += 1
        sleep(2)


app = wx.App()
frame = wx.Frame(None, -1, 'simple.py')
frame.Show()
thread = Thread(target=test)
thread.setDaemon(True)
thread.start()
app.MainLoop()

Upvotes: 2

nepix32
nepix32

Reputation: 3177

This was a quite interesting problem. The issue at hand was not quite obvious (at least to me).

The last line of simple_app.py blocks until frame is closed/destroyed. Therefore if started from main.py the import will finish only (and show output from print), when the frame is closed.

Try the following instead (normally you would structure your program better to start/stop the app where you need it):

In simple_app.py change last line to:

if __name__ == '__main__':
    app.MainLoop()

In main.py

import wx
import simple_app
app = wx.GetApp()
app.MainLoop()

I can not tell you why exactly there is a difference on running directly and on importing (running directly shows the result of print, but importing does not).

Upvotes: 1

Related Questions