Search code examples
pythonpython-3.xmultithreadingpython-3.7pyglet

Loop in Thread that blocks the Script


I'm having a problem that I can't figure out how to solve. Probably the solution is also simple, but at the moment it does not appear in my head. Ah, I didn't know what to write in the title, so if you want to change how you think it's lawful, do it =)

I put a simplified version of the code, where the problem appears:

from pyglet.gl import *
import threading, time, os

FPS = 120.0

class ucgm(pyglet.window.Window):

    window_on_show = False
    grid_set = 32, 18
    old_set = 1024, 576
    new_set = old_set

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__video_set()

        self.__resource = Resource_Load(self)
        s_l = threading.Thread(target=self.__resource.start_load())
        s_l.start()

    def on_show(self):
        print("1")
        self.window_on_show = True
        print("2")

    def on_draw(self):
        self.clear()

    def update(self, dt):
        pass

    def on_close(self):
        os._exit(0)

    def __video_set(self):
        self.__platform = pyglet.window.get_platform()
        self.__default_display = self.__platform.get_default_display()
        self.__default_screen = self.__default_display.get_default_screen()

        self.set_size(self.new_set[0], self.new_set[1])
        self.location = self.__default_screen.width // 2 - self.new_set[0] // 2, self.__default_screen.height // 2 - self.new_set[1] // 2
        self.set_location(self.location[0], self.location[1])

class Resource_Load():

    def __init__(self, main):
        self.engine = main
        self.loop_load = True

    def start_load(self):
        while self.loop_load:
            if self.engine.window_on_show:
                self.loop_load = False
                print("3")
            else:
                print("4")
                time.sleep(1)
        print("5")

if __name__ == "__main__":
    uc = ucgm()
    pyglet.clock.schedule_interval(uc.update, 1 / FPS)
    pyglet.app.run()

Before talking about the problem, I would like to explain what my intention was. What I thought was to create a class parallel to the main one that took care of loading all the resources used. In the meantime, it had to "dialogue" with the main class and modify a variable of it that showed the progress of the upload. Some time ago I asked a similar question on Stackoverflow about something similar (Link Question). The difference is that in that question the parallel function was internal to the main class. But given the amount of resources to load, I decided to dedicate a separate class to him. Once everything was written, I thought of another problem. Although it was written later, the Thread started before the window appeared completely. I thought of adding a simple time.sleep set with a few seconds. But then I reasoned that there was the possibility of a delay, caused by excessive use of RAM by the user, and therefore the time set was not sufficient. So I modified, adding a while loop with the condition that if the window was visible, it would have ended the loop and continued loading, otherwise it would have waited a second. nd here we are finally in the problem. The script as it is written does not work. Or rather, it remains stuck in the cycle, despite being open in a Thread. I knew, from my previous question, that the Thread should not directly touch Pyglet, and in fact it does not happen, since it is only reading the status of a variable. So I don't understand why I'm getting this result. The cycle seems to be not happening in parallel and therefore blocks the on_show function which fails to update the variable and thus to break the cycle. Do you have any suggestions for me to solve the problem I'm having? Thanks for your help


Solution

  • Instead of passing a reference to the start_load function on this line:

    s_l = threading.Thread(target=self.__resource.start_load())
    

    ...that line is actually calling that function (notice the ()), which is running that infinite loop on the main thread. Get rid of that (); threading.Thread will do the call for you:

    s_l = threading.Thread(target=self.__resource.start_load)
    

    Additionally, there are better ways to wait for something to happen in one thread from another. You should consider using one of the thread synchronization object provided in Python, for example, a threading.Event:

    class ucgm(pyglet.window.Window):
        window_opened = threading.Event()
        // ...
        def on_show(self):
            self.window_opened.set()
        // ...
    
    class Resource_Load():
        // ...
        def start_load(self):
            self.engine.window_opened.wait()
            print("done")
        // ...