user1404
user1404

Reputation: 189

How to make tkinter wait for a new thread to complete?

How to make tkinter execution in function fix() wait till label's text get chnaged and then print end.

Current working: When I click the sub button, new thread gets created and for loop gets executed. After 10000000 loops, my label would change to 9999999. But before my label changes, tkinter prints end.

I tried t.join(), but it freezes the GUI.

import tkinter as tk
from tkinter import ttk
import threading

root = tk.Tk()
label = tk.Label(text='vinoth')
label.pack()

def fix():
    a=0
    t = threading.Thread(target=count, args=(a,))
    t.start()
    #t.join()
    print('end')

def count(a):
        for i in range(0,10000000):
            a=i
        label['text'] = a

button = tk.Button(text='sub', command=fix)
button.pack()
dropdown = ttk.Combobox()
dropdown.pack()

root.mainloop()

Upvotes: 3

Views: 4678

Answers (2)

martineau
martineau

Reputation: 123423

You have to be careful multithreading tkinter applications because the interface to tcl/tk doesn't doesn't support it. That means only the main thread can make calls to tkinter and its widgets.

That said, you can workaround the limitation by using the universal after() method to schedule a function to run periodically and communicate with the thread, either through thread-safe mechanisms like a Queue or via global variables coupled with a Lock to control concurrent access to it or them.

Here's an example of using the latter to do what you're trying to accomplish:

import tkinter as tk
from tkinter import ttk
import threading


POLLING_DELAY = 250  # ms
lock = threading.Lock()  # Lock for shared resources.
finished = False

root = tk.Tk()
label = tk.Label(text='vinoth')
label.pack()

def fix():
    global finished

    with lock:
        finished = False
    t = threading.Thread(target=count)
    t.daemon = True
    root.after(POLLING_DELAY, check_status)  # Start polling.
    t.start()

def check_status():
    with lock:
        if not finished:
            root.after(POLLING_DELAY, check_status)  # Keep polling.
        else:
            print('end')

def count():
    global finished

    for i in range(10000000):
        a = i
    with lock:
        finished = True
        label['text'] = a


button = tk.Button(text='sub', command=fix)
button.pack()
dropdown = ttk.Combobox()
dropdown.pack()

root.mainloop()

Upvotes: 5

User1493
User1493

Reputation: 491

I gave this a try by using root.update() function in a while loop. Not sure if this is a right away to do things! But posting the code anyway:

import tkinter as tk
from tkinter import ttk
import threading

root = tk.Tk()

class test():

    def __init__(self):
        self.label = tk.Label(text='vicks')
        self.label.pack()
        button = tk.Button(text='sub', command=self.fix)
        button.pack()
        dropdown = ttk.Combobox()
        dropdown.pack()

    def fix(self):
        self.finished = False
        t = threading.Thread(target=self.count)
        t.daemon = True
        t.start()
        self.check_status()
        print('end')

    def check_status(self):
        while self.finished is False:  #keeps running until the variable finished becomes True
            root.update()

    def count(self):
        a=0
        for i in range(100000000):
            a = i
        self.label['text'] = a
        self.finished = True

c = test()

root.mainloop()

Upvotes: 2

Related Questions