Multithreading with tkinter

Recently, I got stuck with very new problem (for me) of updating GUI in Tkinter when long processes are needed to be run (like running a time-consuming loop, waiting for a process to complete and return or fetching something from URL). Actually, for processes requiring a long time to complete, the Tkinter blocks other GUI events. Because of it, updates to GUI element only happen when the process returns after completing execution.

I earlier didn’t know about the ‘thread safe’ property of python Tkinter. The main caveat here is that we can’t update GUI elements from multiple threads. Once main thread initiates the mainloop(), we can never use other thread to update the GUI. However, we can easily do background processes in other threads. But, here also we need to invoke a GUI function whenever the process in the background thread stops and returns the result. But, a GUI function can only be invoked through the thread executing mainloop().

Therefore, after reading some online resources on StackOverflow and other Python blogs, I came to know about one design pattern followed to solve this problem. Instead of invoking a GUI function, whenever the result is returned we need to maintain a shared queue. The contents of queue will be shared between the thread executing mainloop() and the thread running the background process. Whenever we need to return the result after the process ends in the background thread, we need to put the result in the queue. On the other side, the thread ( executing mainloop() ) needs to periodically check the contents of the shared queue. In my case, I couldn’t understand this concept of ‘shared queue’ just by reading about it. Therefore, let’s go through a small piece of code to understand it better.

def runloop(thread_queue=None):
    '''
    After result is produced put it in queue
    '''
    result = 0
    for i in range(10000000):
         #Do something with result
    thread_queue.put(result)

class MainApp(tk.Tk):

    def __init__(self):
        ####### Do something ######
        self.myframe = tk.Frame(self)
        self.myframe.grid(row=0, column=0, sticky='nswe')
        self.mylabel = tk.Label(self.myframe) # Element to be updated 
        self.mylabel.config(text='No message')
        self.mylabel.grid(row=0, column=0)
        self.mybutton = tk.Button(
            self.myframe, 
            text='Change message',
            command=lambda: self.update_text)
        self.mybutton.grid(row=1, column=0)

    def update_text(self):
        '''
        Spawn a new thread for running long loops in background
        '''
        self.mylabel.config(text='Running loop')
        self.thread_queue = queue.Queue()
        self.new_thread = threading.Thread(
            target=runloop,
            kwargs={'thread_queue':self.new_thread})
        self.new_thread.start()
        self.after(100, self.listen_for_result)

    def listen_for_result(self):
        '''
        Check if there is something in the queue
        '''
        try:
            self.res = self.thread_queue.get(0)
            self.mylabel.config(text='Loop terminated')
        except queue.Empty:
            self.after(100, self.listen_for_result)
        
if __name__ == "__main__":
    root = tk.Tk()
    main_app = MainApp(root)
    root.mainloop()

Here, we may need to disable the button, because it may happen that clicking mybutton may result in creating multiple new threads.

Advertisements

Author: scorpiocoder

I am a 3rd year, IIIT-Chittoor student enthusiastic and confused about various software technologies : Python, Machine Learning, Networks, Natural Language Processing and Android Development

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s