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)))