Home > Back-end >  Using Thread in TKinter GUI
Using Thread in TKinter GUI

Time:10-27

I'm working on a Tkinter Desktop Application project. I started stumbling when I need to use Threading which runs the backend code. I know that in order to share the variables across threads we should be using a global variable. Below is the minimal code.

obj = None

class Frame:
    def __init__(self, frame):
        self.middle_frame = frame
        self.start_button = ttk.Button(self.middle_frame, text='Start', command=self.start)
        self.start_button.grid(row=0, column=0)
        self.stop_button = ttk.Button(self.middle_frame, text='Stop', command=self.stop)
        self.stop_button.grid(row=0, column=1)
        self.stop_button.config(state='disabled')

    def start(self):
        self.thread = threading.Thread(target=self.start_connection)
        self.thread.start()
        self.start_button.config(state='disabled')
        self.stop_button.config(state='normal')
    
    def start_connection(self):
        global obj
        obj = MainManager() # Starts the Backend Loop


    def stop(self):
        global obj
        obj.close_connection() # Want to break the loop here
        self.thread.join()


        self.stop_button.config(state='disabled')
        self.start_button.config(state='normal')
        

While running this code, I get obj.close_connection() AttributeError:'NoneType' object has no attribute 'close_connection'. But I was expecting obj to become as an object of MainManager(). Where am I going wrong? Help me with this.

CodePudding user response:

The problem with your code is nothing to do with tkinter or really with multithreading. The problem is that MainManager has the loop started inside the __init__ method, something like this:

class MainManager:
    def __init__(self):
        self.alive = True

        # this part runs until stopped
        self.start_doing_stuff()

    def start_doing_stuff(self):
        while self.alive:
            sleep(1)
            print('doing stuff')

    def stop(self):
        self.alive = False

Here is a code snipped with a similar error to yours:

from threading import Thread
from time import sleep

obj = None


class MainManager:
    def __init__(self):
        self.alive = True

        self.start_doing_stuff()

    def start_doing_stuff(self):
        while self.alive:
            sleep(1)
            print('doing stuff')

    def stop(self):
        self.alive = False


def test():
    global obj
    print('about to assign to my_global')
    obj = MainManager()
    print('assigned to my_global')


print(f'{obj=}')
t = Thread(target=test)
t.start()
sleep(3)
print(f'{obj=}')
obj.stop()
t.join()

Because the __init__ method doesn't terminate until the stop method is called, the call to MainManager() in the statement obj = MainManager() never terminates, so obj is not assigned to. This means that when obj.stop() is called, obj is still None.

This can be fixed my making the __init__ method terminate, and putting the long-running code into a separate method that is called later:

from threading import Thread
from time import sleep

obj = None


class MainManager:
    def __init__(self):
        self.alive = True

    def start_doing_stuff(self):
        while self.alive:
            sleep(1)
            print('doing stuff')

    def stop(self):
        self.alive = False


def test():
    global obj
    print('about to assign to my_global')
    obj = MainManager()
    print('assigned to my_global')
    obj.start_doing_stuff()


print(f'{obj=}')
t = Thread(target=test)
t.start()
sleep(3)
print(f'{obj=}')
obj.stop()
t.join()

CodePudding user response:

Have you imported MainManager, have you run the start_connection()?

Also, you delegate the assignment to a thread. Are you sure this affects the same obj as the main thread? Maybe the main thread stays 'none' and the delegate changes the value in a copy. Add a log or print statement and make sure the obj gets assigned the MainManager() object and print the memory location of that object. In the main class print the memory location of the obj variable there as well. Check if it is the same.

This is how you print the memory address of a variable :

print(hex(id(your_variable)))
  • Related