0

When my program executes the python GUI freezes. Here is my main code. Can I get some help in doing threading? So the execution happens in the background and I can still be able to use the "x" button in the GUI if I want to end the execution? Currently I just ask the user to close the cmd to end the program.

if __name__ == "__main__":

    root = Tk() 
    root.title('Log')
    root.geometry("400x220") 
    font1=('times', 15)
    font2=('times', 10)
    #Label inside root 
    Label(root, relief=GROOVE, font=font2, text="level").pack() 
    variable = StringVar(root)
    variable.set("INFO") # default value

    w = OptionMenu(root, variable, "CRITICAL", "DEBUG")
    w.pack()
    Button(root, font=font1, background= "yellow", text='START',command=main).pack()
    Label(root, text="To end just close the CMD window").pack()

    root.mainloop()
guylifestyle
  • 317
  • 1
  • 6
  • 13
  • 3
    What exactly is suppose to be done in the thread? Are you planning on updating the GUI? Just a note, Tkinter is not thread safe. – Matt Jul 01 '14 at 15:40
  • Where is `main` function ? Where is code with problem ? Maybe you could do this with `tk.After()` ? – furas Jul 01 '14 at 15:51

1 Answers1

1

UPDATE: Turns out the Button callback was autorunning launch because the function object wasn't being set as the callback, the called function itself was. The fix is to replace the callback lambda: spawnthread(fcn) so that a function object is set as the callback instead. The answer has been updated to reflect this. Sorry for missing that.


The GUI mainloop will freeze when you try to run some other function, and has no way to restart itself (because it's frozen.)

Let's say the command you'd like to run alongside the GUI mainloop is myfunction.

Imports:

import time
import threading
import Queue

You need to set up a ThreadedClient class:

class ThreadedClient(threading.Thread):
    def __init__(self, queue, fcn):
        threading.Thread.__init__(self)
        self.queue = queue
        self.fcn = fcn
    def run(self)
        time.sleep(1)
        self.queue.put(self.fcn())

def spawnthread(fcn):
    thread = ThreadedClient(queue, fcn)
    thread.start()
    periodiccall(thread)

def periodiccall(thread):
    if(thread.is_alive()):
        root.After(100, lambda: periodiccall(thread))

You then want the widget calling the function to instead call a spawnthread function:

queue = Queue.Queue()

Button(root, text='START',command=lambda: spawnthread(myfunction)).pack() #<---- HERE

N.B. I'm adapting this from a multithreaded tkinter GUI I have; I have all my frames wrapped up in classes so this might have some bugs since I've had to tweak it a bit.

Al.Sal
  • 984
  • 8
  • 19
  • I keep getting error line 26, in spawnthr ead periodiccall(thread) File X, line 30, in periodic call after(100, lambda: periodiccall(thread)) NameError: global name 'after' is not defined – guylifestyle Jul 01 '14 at 19:52
  • Ah. Try using root.after(...) instead. – Al.Sal Jul 01 '14 at 20:03
  • @user2980048 or even root.After(...). I haven't used tkinter in awhile, I'm not sure which one is best. – Al.Sal Jul 02 '14 at 12:40
  • thanks that is working now after using root.After(..). However, why is my main() function executing without me even clicking the start button? – guylifestyle Jul 02 '14 at 13:56
  • Did it run without clicking the start button before? – Al.Sal Jul 02 '14 at 14:06
  • Yes it ran without clicking the start button. And when I click the close button in the ticker it didn't stop the background process that was started. – guylifestyle Jul 02 '14 at 14:13
  • Consider renaming main() and seeing what happens. – Al.Sal Jul 02 '14 at 14:19
  • same thing...called it Button(root, font=font1, background= "yellow", text='START',command=spawnthread(launch)).pack() – guylifestyle Jul 02 '14 at 14:21
  • Hm. So main/launch/whatever ran automatically even before you added the threading before? – Al.Sal Jul 02 '14 at 14:56
  • no, it only started doing this when I added spawnthread. If i remove that from the line above it works fine. Meaning it starts the process after clicking the start button. – guylifestyle Jul 02 '14 at 15:24
  • So running the program without multithreading doesn't autostart it. Okay. Try moving the `queue = Queue.Queue()` line to right after the Button line (ideally right before the `def spawnthread` line) – Al.Sal Jul 02 '14 at 15:46
  • are you placing the queue in the if __name__ == "__main__":? And wouldn't it not complain about no defining spawnthread if you put it after button line? – guylifestyle Jul 02 '14 at 16:07
  • with queue before the spawnthread method it still behaves the same. – guylifestyle Jul 02 '14 at 16:12
  • Try it out. I'm not in at a place where I can test right now. I see what you mean though. The function defs and class declarations should be outside of the name == 'main'. The queue line should be within. – Al.Sal Jul 02 '14 at 16:13
  • Okay, edited again. In its current incarnation it is still auto-starting? – Al.Sal Jul 02 '14 at 16:14
  • that is how i had it originally. So, yes, it does auto-start. – guylifestyle Jul 02 '14 at 16:22
  • Did some testing on my computer and I got it. What was happening was that the button callback was autorunning the function passed into it. It has to be a lambda function instead. Check the updated answer (the change is in the button line.) I also removed the font and background to make it easier to see the change. – Al.Sal Jul 03 '14 at 20:50
  • thanks a lot. Basically using lambda allowed us to pass function to another function – guylifestyle Jul 08 '14 at 19:14
  • one last question hopefully. Now that the GUI is not freezing I thought about adding a stop button under the start button, which will kill the processes that started with start button. I just put in a stop button and then did the command = stop. In which, I just did sys.exit(). I guess that is the wrong way to exit. Any suggestions? – guylifestyle Jul 08 '14 at 19:31
  • Try playing around with `thread.stop()` in the `periodiccall` function. – Al.Sal Jul 08 '14 at 19:53