Search code examples
pythonmultithreadingpygamepython-multithreading

Threading to update separate process while not lagging main process


Apologizes if this is simple, I just don't know what I am doing wrong.

I need to update information from a website I am scraping every 10 seconds (so as not to overload the website). I want to display the information from the website using pygame. I have tried using the threading module. However, when I make the thread sleep, it sleeps the whole program.

For example, when I do something like:

def func():
    print('Hello1')
    time.sleep(4)
    print('Hello2')

class Display():

    def __init__(self):

        pg.init()

        self.win = pg.display.set_mode((WIN_WIDTH,WIN_HEIGHT))
        pg.display.set_caption(WIN_TITLE)
    def thread_function(self):
        x = threading.Thread(target = func)
        x.start()

    def initialize_display(self):
        run = True
    while run:
        self.win.fill(WHITE)
        self.thread_function()
        for event in pg.event.get():

            if event.type == pg.QUIT:
                run = False
                #deactivate pg library
                pg.quit()
                #quit program
                quit()
        pg.display.update()

if __name__ == "__main__":
    display = Display()
    display.initialize_display()

The whole program waits 4. How can I make it so only that process of func() is executed after 4 seconds?


Solution

  • Are you sure that you want to start a new thread every frame? Maybe just create the thread once and start another loop inside the target function of the thread, or wrap that function with another one that also handles exiting the thread gracefully.

    You could do something like this:

    import pygame as pg
    import threading
    import time
    import signal
    WIN_WIDTH,WIN_HEIGHT=(400,400)
    WIN_TITLE=''
    WHITE='white'
    
    def func():
        print('Hello1')
        time.sleep(4)
        print('Hello2')
    
    class Display():
    
        def __init__(self):
            pg.init()
            self.win = pg.display.set_mode((WIN_WIDTH,WIN_HEIGHT))
            pg.display.set_caption(WIN_TITLE)
        
        def thread_function(self, should_stop):
            print('[Display] starting thread')
            
            # 'func' is wrapped in this wrapper function
            # the wrapper loops until the 'should_stop'-callback returns true
            def wrapper(should_stop):
                print('start doing something')
                while not should_stop():
                    func()
                print('stop doing something')
                
            x = threading.Thread(target = wrapper, args = [should_stop])
            x.start()
            return x
            
        def initialize_display(self):
            # run could also be an attribute of Display ....
            run = True
    
            # we keep a reference of the thread to we can wait
            # for it to finish once we told it to stop
            thread = self.thread_function(lambda: not run)
    
            # when the process should be killed (e.g. CTRL+C), we also want
            # to stop the thead by setting 'run' to false
            def stop(sig, frame):
                print('[Display] got killed, stop thread')
                nonlocal run 
                run = False
            
            signal.signal(signal.SIGINT, stop)
    
            # just a little moving Rect so we see the main loops works
            # fine while the thread runs in the background
            r = pg.Rect((0, 200, 20, 20))
            d = 1
            clock = pg.time.Clock()
            while run:
                self.win.fill(WHITE)
                
                for event in pg.event.get():
                    if event.type == pg.QUIT:
                        # setting 'run' to false will also stop the loop
                        # of the wrapper function
                        run = False
                        print('[Display] waiting for thread to finish')
                        # let's wait for the thread to finish
                        thread.join()
                        return
                        
                pg.draw.rect(self.win, 'dodgerblue', r)
                r.move_ip(d, 0)
                if not self.win.get_rect().contains(r):
                    d *= -1
                pg.display.update()
                clock.tick(60)
                
    if __name__ == "__main__":
        display = Display()
        display.initialize_display()