0

I am trying to use multithreading to show a loading wheel while doing some background calculations. I try to start the multithreaded function, and then run a timer to simulate the calculations. The problem is the thread first starts running when the timer is over, even though I am starting the thread first...

from tkinter import *
from tkinter import messagebox
import time
from threading import *
from PIL import Image, ImageTk
# Create Object
root = Tk()
  
# Set geometry
root.geometry("400x400")
flag = True
# use threading
def threading():
    t1=Thread(target=work)
    t1.start()
    time.sleep(10)
# work function
def work():
    print("sleep time start")
    image = Image.open("Static/spinner0.png")
    img = ImageTk.PhotoImage(image)
    lab = Label(root,image=img)
    lab.pack()
    while flag:
        for i in range(8):
            image = Image.open("Static/spinner"+str(i)+".png")
            img = ImageTk.PhotoImage(image)
            lab['image'] = img
            time.sleep(0.2)
        #return False
    print("sleep time stop")

def on_closing():
    if messagebox.askokcancel("Quit", "Do you want to quit?"):
        flag = False
        root.destroy()
        
# Create Button
Button(root,text="Click Me",command = threading).pack()

# Execute Tkinter
root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()
VictorBjo
  • 26
  • 3
  • is `time.sleep(10)` a replacement for the calculations ? – Ahmed AEK Sep 21 '22 at 10:40
  • 3
    `tkinter` won't like you modifying the GUI from a different thread. – quamrana Sep 21 '22 at 10:44
  • @quamrana, it actually allows it very nicely, it just has to be done correctly. – Ahmed AEK Sep 21 '22 at 10:45
  • @AhmedAEK: Do you have any references for that? – quamrana Sep 21 '22 at 10:46
  • not a reference, but i have applications that work with threads that are updating the GUI in different threads, but tk documentation is a mess. – Ahmed AEK Sep 21 '22 at 10:47
  • 2
    Your `time.sleep(10)` blocks `tkinter` from running. Once that has finished, `tkinter` can resume. – quamrana Sep 21 '22 at 10:47
  • @AhmedAEK Yes, the time.sleep(10) is a replacement for my calculations. When doing it with my actual calculations, same result – VictorBjo Sep 21 '22 at 10:47
  • I have only been able to use threads by sending [GUI modification] commands back to the main thread through a queue. – quamrana Sep 21 '22 at 10:48
  • I got it working somewhat, by running the calculations in another thread. However, I can see that my first thread is slowed down significantly by this... – VictorBjo Sep 21 '22 at 10:49
  • Yes, your calculations should be done in a separate thread and the GUI code should be in the main thread. However, if the calculations are CPU bound, you might want to consider multiprocessing. – quamrana Sep 21 '22 at 10:50
  • 3
    yes, using `time.sleep` is bad in applications run by tk, you should instead use something like the `root.after` method. – Ahmed AEK Sep 21 '22 at 10:50
  • 1
    well, the proper way to do it is to run the calculations in different thread, and have your main thread update the GUI using a method similar to [this answer](https://stackoverflow.com/questions/73745376/python-tkinter-label-not-responding/73745555#73745555) instead of using `time.sleep` – Ahmed AEK Sep 21 '22 at 10:52
  • @quamrana tk is only waiting for input so it has 0 CPU usage if the screen is not changing, so it's okay to use multithreading with tk for calculations. – Ahmed AEK Sep 21 '22 at 10:54
  • 1
    I've got my own [answer](https://stackoverflow.com/a/59327161/4834) to link to. – quamrana Sep 21 '22 at 10:57
  • not blocking the main event loop and updating GUI from multiple threads are two separate problems, you can have 10 extra threads of which 9 are updating the GUI simultaneously, and 1 doing calculations, just don't interrupt the eventloop running on your main thread unless you absolutely have to, like responding to button presses. – Ahmed AEK Sep 21 '22 at 11:03
  • Remove line 16 time.sleep(10). Worked for me. – toyota Supra Sep 21 '22 at 12:37

1 Answers1

0

Just putting the answer here to avoid people digging comments for the solution.

One way to do this is to have both the calculations and the updating function running on extra different threads, this way the main process event loop stays uninterrupted and you get to call time.sleep, which is fine so long as it doesn't happen in your main thread.

A thread in python consumes below 100KB of memory (plus memory for variables in code running on it), so it's fine to have multiple threads running.

from tkinter import *
from tkinter import messagebox
import time
from threading import *
from PIL import Image, ImageTk

# Create Object
root = Tk()

# Set geometry
root.geometry("400x400")
flag = True


# use threading
def threading():
    t1 = Thread(target=work)
    t1.start()
    t2 = Thread(target=time.sleep,args=(10,)) # the heavy computation function.
    t2.start()
    # control is given back to the main event loop

# work function
def work():
    print("sleep time start")
    image = Image.open("Static/spinner0.png")
    img = ImageTk.PhotoImage(image)
    lab = Label(root,image=img)
    lab.pack()
    while flag:
        for i in range(8):
            image = Image.open("Static/spinner"+str(i)+".png")
            img = ImageTk.PhotoImage(image)
            lab['image'] = img
            time.sleep(0.2)
        #return False
    print("sleep time stop")


def on_closing():
    if messagebox.askokcancel("Quit", "Do you want to quit?"):
        flag = False
        root.destroy()


# Create Button
Button(root, text="Click Me", command=threading).pack()

# Execute Tkinter
root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()
TheLizzard
  • 7,248
  • 2
  • 11
  • 31
Ahmed AEK
  • 8,584
  • 2
  • 7
  • 23
  • 1
    you shouldn't call `tkinter` stuff from other threads – Matiiss Sep 21 '22 at 11:34
  • @Matiiss any reason not to do so ? .. and are you sure you are correctly referring to `tkinter` not `QT` ? – Ahmed AEK Sep 21 '22 at 11:34
  • 1
    not confusing anything, the reason is that it may make `tkinter`'s interpreter crash, so calling from other threads should be simply avoided, you can also somewhat refer to quamrana's comment, it's just a known thing that you shouldn't be calling `tkinter` stuff from other threads – Matiiss Sep 21 '22 at 11:36
  • 1
    @ Mastiss is right about the dangers of calling `tkinter` methods from other threads. For an example look [here](https://pastebin.com/wF5Fn19a). Also you missed `global flag` at the start of `on_closing`. To improve your answer look at [this](https://stackoverflow.com/a/459131/11106801). – TheLizzard Sep 21 '22 at 13:49