2

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()
user1404
  • 179
  • 1
  • 3
  • 10
  • I will be replacing the line `print('end')` with many lines of code, tkinter multi threading does not support those. So I have to perform the work in new thread and come back to main thread. Please guide me how to put a pause in function `fix` till thread `t` completes. – user1404 May 06 '19 at 16:14
  • You want something like this: [how to wait until callback has completed](https://stackoverflow.com/a/55872876/7414759) – stovfl May 06 '19 at 16:22

2 Answers2

4

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()
martineau
  • 119,623
  • 25
  • 170
  • 301
  • That was beautiful! Can you please provide an example for Queue as well? I would really appreciate it. – User1493 May 06 '19 at 20:16
  • @User1493: If my answer solves your problem, please accept it. FYI, in the past I once posted an [answer](https://stackoverflow.com/a/53697547/355230) to a related question about waiting for thread completion that uses a `Queue`. – martineau May 06 '19 at 20:40
  • @martineau, thanks for your support. But the code you shared never prints `end` – user1404 May 07 '19 at 09:56
  • How would the variable `finished` become `True`, when the `daemon thread` completes execution? – user1404 May 07 '19 at 10:07
  • @user1404: The `count()` functions set it to `True` after the `for` loop. However I forgot to declare it `global` in a couple of functions that change its value, which is why it didn't print `end`—although it's a lame excuse, the reason is simply because I don't use globals much myself and simply forgot… – martineau May 07 '19 at 13:55
  • Yeah, found that sometime back. Thank you very much! – user1404 May 07 '19 at 16:10
2

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()
User1493
  • 481
  • 2
  • 7
  • 23
  • This looks very simple and on point. Thank you very much! Seems like there are a lot of ways to achieve things. – user1404 May 07 '19 at 16:11