I'm attempting to create a Tkinter GUI that runs the mainloop in its own thread, enabling me to run things in other threads (which potentially involves updating elements in the Tkinter GUI). I have the construct below.
import tkinter as tk
from threading import Thread
class DisplayWindow(object):
def __init__(self):
self.running = False
def start(self):
self.running = True
self.thread = Thread(target = self.run)
self.thread.start()
def callback(self):
self.running = False
self.root.destroy()
def run(self):
self.root = tk.Tk()
self.root.protocol('WM_DELETE_WINDOW', self.callback)
self.root.geometry('600x600')
tk.Label(self.root, text='Hello World').pack()
self.root.mainloop()
I can do something like the following with this
win = DisplayWindow()
win.start()
My understanding of how this works is that when win.start() is called the first time, a new thread is created, set to run the run method, and the thread is started. This thread executes the creation of a Tkinter GUI that runs the mainloop until the window is closed (at which time the thread should become inactive). If I close the window and call win.start() again, I expect that the second call should just repeat the process, creating a new thread that calls the run method. However, calling start a second time simply crashes python.
What am I doing wrong?
Related
My question is regarding initializing a GUI built with tkinter. I'm starting another thread within the GUI that's running a script which takes 10 minutes to finish. The reason I'm doing this in another thread is to be able to keep the GUI responsive during these 10 minutes.
Right now I'm trying to do the following (simplified)
import tkinter as tk
from threading import Thread
class GUI:
def __init__(self):
self.master = tk.Tk()
self.master.geometry("1400x700")
//
...
//
self.master.mainloop()
def run_long_script(self): # Called by button in GUI
self.t1 = Thread(target = long_script)
self.start()
def long_script(self):
try:
...
except InterruptedError as error:
GUI()
This works okay, but when I try to close the GUI with long_script running, I get the error message main thread not in main loop. How should I design the code to be able to close the program correctly?
Tkinter like many other GUIs can use widgets only in main thread
(or rather in thread which runs mainlooop).
Other thread has to update global value (which is shared between threads) or use queue to send value to main thread. And main thread has to use after(milliseconds, function_name) to run periodically function which will get value from global variable or from queue and update progress bar.
Minimal working code.
import threading
import time
import tkinter as tk
import tkinter.ttk as ttk
# --- functions ---
def long_script():
global progress_value
for i in range(20):
print('loop:', i)
# update global variable
progress_value += 5
time.sleep(.5)
def run_long_script():
global progress_value
global t
if t is None: # run only one thread
# set start value
progress_value = 0
# start updating progressbar
update_progressbar()
# start thread
t = threading.Thread(target=long_script)
t.start()
else:
print('Already running')
def update_progressbar():
global t
# update progressbar
pb['value'] = progress_value
if progress_value < 100:
# run it again after 100ms
root.after(100, update_progressbar)
else:
# set None so it can run thread again
t = None
# --- main ---
# default value at start
progress_value = 0
t = None
# - gui -
root = tk.Tk()
pb = ttk.Progressbar(root, mode="determinate")
pb.pack()
b = tk.Button(root, text="start", command=run_long_script)
b.pack()
root.mainloop()
im trying to learn python. But i have problems with the threading. First i failed in the "Proces" class because i putted the loop on the wrong place and my program newer returned from the other class.
But now i think all is correct and it still does not work. I need to have a GUI where i want to be able to write my conditions via text entries and i need another class "Proces" that will do stuff, checking status ower internet and so on constantly or in a specified interval...
The Problem is that my tkinter GUI is freezing after pressing something
here is my GUI.py file:
import tkinter as tk
from Proces import Proces
root = tk.Tk()
frame = tk.Frame(root)
frame.pack()
button = tk.Button(frame, text="QUIT", fg="red",command=quit).pack(side=tk.LEFT)
pr = Proces()
print("\nGUI: proces init...")
pr.start()
print("\nGUI: Start ended")
root.mainloop()
here is the Proces.py file:
import time, threading
class Proces(threading.Thread):
def loop(self):
while True:
time.sleep(2)
print("\nProces: looping")
def __init__(self):
threading.Thread.__init__(self)
print("\nProces: Starting proces")
time.sleep(2)
def run(self):
self.deamon = True
print("\nProces: Starting loop")
self.loop()
*This is the output: *
Proces: Starting proces
GUI: proces init...
Proces: Starting loop
GUI: Start ended
Proces: looping
Proces: looping
Proces: looping
Proces: looping
*But the GUI of the tkinter does not react.*
How should i do this kind of task?
Thank you for your help, advice and answer
I think you don't have problem about starting thread etc. However, you should be able to control your thread, meaning your thread should return based on some condition. It seems you have a button to quit. I assume you want to finish the process by clicking the button.
To do that, when you click to button, your main thread should pass a variable to Process in order to break your while loop, which is your thread basically.
Here is a code that you can work on.
import tkinter as tk
import time
import threading
class MainWindow:
def __init__(self, master):
self.master = master
self.quit_button = tk.Button(self.master, text="QUIT", command=lambda:self.quit(), width=20)
self.quit_button.pack(side=tk.LEFT)
self.process = None
print("\nGUI: process init...")
self.start_task()
def quit(self):
self.process.stop_process = True
print("\nGUI: Start ended")
def start_task(self):
self.process = Process()
self.process.start()
class Process(threading.Thread):
def loop(self):
while True:
if not self.stop_process:
time.sleep(2)
print("\nProcess: looping")
else:
print("\nProcess: looping ended")
return
def __init__(self):
threading.Thread.__init__(self)
self.stop_process = False
print("\nProcess: Starting proces")
time.sleep(2)
def run(self):
self.deamon = True
print("\nProcess: Starting loop")
self.loop()
if __name__ == '__main__':
root = tk.Tk()
app = MainWindow(master=root)
root.mainloop()
So you start yout tkinter, which is your main thread. Then iniating another class within your main thread, which is inherits the thread. So you have two seperate thread that working. When you clicked to "quit" button, you pass variable stop_process, which breaks the loop and return, meaning ending your thread. Your main thread is still alive for running your tkinter window.
I hope it will help
Can somebody help me please, I'm making an exercise about class and running task on other thread then tkinter. I want to change the label in another class. Can't get my script to work.
I tried different things but I'm having some troubles with understanding the inheriting from classes and the threads, so this is just an example to learn more about it.
from tkinter import *
import tkinter as tk
from tkinter import ttk
import threading
#Gloabl for stopping the run task
running = True
#class 1 with window
class App():
def __init__(self):
#making the window
self.root = tk.Tk()
self.root.geometry("400x400+300+300")
self.root.protocol("WM_DELETE_WINDOW", self.callback)
self.widgets()
self.root.mainloop()
# stop task and close window
def callback(self):
global running
running = False
self.root.destroy()
# all the widgets of the window
def widgets(self):
global labelvar
#startbutton
self.start_button = tk.Button(self.root, text="Start", command=lambda:App2())
self.start_button.pack()
#stopbutton
self.stop_button = tk.Button(self.root, text="Stop", command=lambda:self.stop())
self.stop_button.pack()
#Defining variable for text for label
labelvar = "Press start to start running"
self.label = tk.Label(self.root, text=labelvar)
self.label.pack()
#stop the task
def stop(self):
global running
running = False
#class 2 with task in other thread
class App2(threading.Thread):
def __init__(self):
global running
#check if task can be run
running = True
threading.Thread.__init__(self)
self.start()
def run(self):
#starting random work
for i in range(10000):
print(i)
labelvar = "running"
App.label.pack()
#checking if task can still be running else stop task
if running == False:
break
labelvar = "stopped"
App.label.pack()
#initiate main app
app = App()
As I said in a comment, tkinter doesn't support multithreading itself, but you can do it as long as only one thread, usually the main one, uses (or "talks") it.
If you want to affect what the GUI displays, the other thread(s) must communicate somehow with the GUI thread. This is often done through a queue.Queue, but in this relatively simple case it can be done through a global variable provided that concurrent access to it is controlled by some means—sharing memory space (i.e. global variables) is one of the advantages of multithreading vs multitasking, but it has to be done and done correctly.
An easy way to share a resource like this is by using a threading.Lock dedicated for that purpose. (See the Wikipedia article Lock (computer science) for more details.)
All references to this shared resource (the running flag) should only be done after "acquiring" the Lock and "releasing" it afterwards. Fortunately it's trivial to do this using a Python with statement (as shown below).
Another crucial aspect of the multithreading problem is how any information exchanged between the two threads is processed. In this case I choose to make the tkinter thread poll the running flag, watch for changes, and update any affected widgets accordingly. This can be done by using the universal widget method after() which tells tkinter to schedule a future call (inside the 'mainloop') to a user-supplied function or method and to pass it certain arguments. To get this to happen repeatedly, the called function can reschedule itself to run again by calling after() before it finishes.
Below is a modified version of your code that does these thing. Note that App2 never calls tkinter or touches any of its widgets, which is why it works.
import threading
from time import sleep
from tkinter import *
import tkinter as tk
from tkinter import ttk
DELAY = 100 # millisecs between status label updates
# global flag and a Lock to control concurrent access to it
run_flag_lock = threading.Lock()
running = False
# class 1 with window
class App():
def __init__(self):
global running
self.root = tk.Tk()
self.root.geometry("400x400+300+300")
self.root.protocol("WM_DELETE_WINDOW", self.quit)
self.create_widgets()
with run_flag_lock:
running = False
self.root.after(DELAY, self.update_status, None) # start status widget updating
self.root.mainloop()
# create all window widgets
def create_widgets(self):
self.start_button = tk.Button(self.root, text="Start", command=self.start)
self.start_button.pack()
self.stop_button = tk.Button(self.root, text="Stop", command=self.stop)
self.stop_button.pack()
self.status_label = tk.Label(self.root, text='')
self.status_label.pack()
def update_status(self, run_state):
""" Update status label text and state of buttons to match running flag. """
# no need to declare run_flag_lock global since it's not being assigned a value
with run_flag_lock:
if running != run_state: # status change?
if running:
status_text = 'Press Stop button to stop task'
run_state = True
else:
status_text = 'Press Start button to start task'
run_state = False
self.status_label.config(text=status_text)
# also update status of buttons
if run_state:
self.start_button.config(state=DISABLED)
self.stop_button.config(state=ACTIVE)
else:
self.start_button.config(state=ACTIVE)
self.stop_button.config(state=DISABLED)
# run again after a delay to repeat status check
self.root.after(DELAY, self.update_status, run_state)
# start the task
def start(self):
global running
with run_flag_lock:
if not running:
app2 = App2() # create task thread
app2.start()
running = True
# stop the task
def stop(self):
global running
with run_flag_lock:
if running:
running = False
# teminate GUI and stop task if it's running
def quit(self):
global running
with run_flag_lock:
if running:
running = False
self.root.destroy()
# class 2 with task in another thread
class App2(threading.Thread):
def __init__(self):
super(App2, self).__init__() # base class initialization
self.daemon = True # allow main thread to terminate even if this one is running
def run(self):
global running
# random work
for i in range(10000):
print(i)
# Normally you shouldn't use sleep() in a tkinter app, but since this is in
# a separate thread, it's OK to do so.
sleep(.25) # slow printing down a little
# stop running if running flag is set to false
with run_flag_lock:
if not running:
break # stop early
with run_flag_lock:
running = False # task finished
# create (and start) main GUI app
app = App()
I've heard that threads in Python are not easy to handle and they become more tangled with tkinter.
I have the following problem. I have two classes, one for the GUI and another for an infinite process. First, I start the GUI class and then the infinite process' class. I want that when you close the GUI, it also finishes the infinite process and the program ends.
A simplified version of the code is the following:
import time, threading
from tkinter import *
from tkinter import messagebox
finish = False
class tkinterGUI(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global finish
#Main Window
self.mainWindow = Tk()
self.mainWindow.geometry("200x200")
self.mainWindow.title("My GUI Title")
#Label
lbCommand = Label(self.mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
#Start
self.mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
class InfiniteProcess(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global finish
while not finish:
print("Infinite Loop")
time.sleep(3)
GUI = tkinterGUI()
GUI.start()
Process = InfiniteProcess()
Process.start()
When I click in the close button (in the upper right corner) the following error appears in the console:
Tcl_AsyncDelete: async handler deleted by the wrong thread
I don't know why it happens or what it means.
All Tcl commands need to originate from the same thread. Due to tkinter's
dependence on Tcl, it's generally necessary to make all tkinter gui statements
originate from the same thread. The problem occurs because
mainWindow is instantiated in the tkinterGui thread, but -- because mainWindow is an attribute of tkinterGui -- is not destroyed until tkinterGui is destroyed in the main thread.
The problem can be avoided by not making mainWindow an attribute of tkinterGui
-- i.e. changing self.mainWindow to mainWindow. This allows mainWindow to be destroyed when the run method ends in the tkinterGui thread. However, often you can avoid threads entirely by using mainWindow.after calls instead:
import time, threading
from tkinter import *
from tkinter import messagebox
def infinite_process():
print("Infinite Loop")
mainWindow.after(3000, infinite_process)
mainWindow = Tk()
mainWindow.geometry("200x200")
mainWindow.title("My GUI Title")
lbCommand = Label(mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
mainWindow.after(3000, infinite_process)
mainWindow.mainloop()
If you want to define the GUI inside a class, you can still do so:
import time, threading
from tkinter import *
from tkinter import messagebox
class App(object):
def __init__(self, master):
master.geometry("200x200")
master.title("My GUI Title")
lbCommand = Label(master, text="Hello world",
font=("Courier New", 16)).place(x=20, y=20)
def tkinterGui():
global finish
mainWindow = Tk()
app = App(mainWindow)
mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
def InfiniteProcess():
while not finish:
print("Infinite Loop")
time.sleep(3)
finish = False
GUI = threading.Thread(target=tkinterGui)
GUI.start()
Process = threading.Thread(target=InfiniteProcess)
Process.start()
GUI.join()
Process.join()
or even simpler, just use the main thread to run the GUI mainloop:
import time, threading
from tkinter import *
from tkinter import messagebox
class App(object):
def __init__(self, master):
master.geometry("200x200")
master.title("My GUI Title")
lbCommand = Label(master, text="Hello world",
font=("Courier New", 16)).place(x=20, y=20)
def InfiniteProcess():
while not finish:
print("Infinite Loop")
time.sleep(3)
finish = False
Process = threading.Thread(target=InfiniteProcess)
Process.start()
mainWindow = Tk()
app = App(mainWindow)
mainWindow.mainloop()
#When the GUI is closed we set finish to "True"
finish = True
Process.join()
The fix here is simple, but hard to discover:
Call mainWindow.quit() immediately after mainwindow.mainloop(), so that the cleanup happens on the same thread as the one that created the tk UI, rather than on the main thread when python exits.
I've heard that threads in Python are not easy to handle and they become more tangled with tkinter.
I have the following problem. I have two classes, one for the GUI and another for an infinite process (I MUST use classes for both). First, I start the GUI class and then the infinite process' class. I want that when you close the GUI, it also finishes the infinite process and the program ends.
A simplified version of the code is the following:
import time, threading
from tkinter import *
from tkinter import messagebox
class Interface(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.attrib1 = "Attrib from Interface class"
def run(self):
#Main Window
self.mainWindow = Tk()
self.mainWindow.geometry("200x200")
self.mainWindow.title("My GUI Title")
self.mainWindow.protocol("WM_DELETE_WINDOW", self.quit)
#Label
lbCommand = Label(self.mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
#Start
self.mainWindow.mainloop()
#The Interface class contains methods that use attributes from itself and attributes from Process class.
def method1(self):
print(self.attrib1)
print(SecondThread.attrib2)
def quit(self):
if messagebox.askyesno('App','Are you sure you want to quit?'):
#In order to use quit function, mainWindow MUST BE an attribute of Interface.
self.mainWindow.destroy()
self.mainWindow.quit()
class Process(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.attrib2 = "Attrib from Process class"
def run(self):
global finish
while not finish:
print("Proceso infinito")
#Inside the infinite process a method from Interface class is used.
GUI.method1()
time.sleep(3)
finish = False
#Starts the GUI
GUI = Interface()
GUI.start()
#Starts the infinity process
SecondThread = Process()
SecondThread.start()
#Waits until GUI is closed
GUI.join()
print("When GUI is closed this message appears")
#When GUI is closed we set finish to True, so SecondThread will be closed.
finish = True
#After all the program should finish but it raises the error: Tcl_AsyncDelete: async handler deleted by the wrong thread
I would appreciate your help!
This occurs because you created the Tk main window on a thread and you don't have the UI running on the processes main thread. When you exit the process the cleanup is being done from the process primary thread. The simplest solution for your example here is to create the UI on the primary thread (the processes default thread) and only use another thread for the worker task. If your real application cannot create the UI on the primary thread you will need to look into terminating Tk from its own thread. Deleting the Tcl interpreter might do that for you.
I modified the example code to show that keeping the UI on the main thread avoids this error message. As you want your worker to be created after the UI is created but before it is running we can use the Tk after method to start the worker once the Tk mainloop is running.
import time, threading
from tkinter import *
from tkinter import messagebox
class Interface:
def __init__(self):
#threading.Thread.__init__(self)
self.attrib1 = "Attrib from Interface class"
#Main Window
self.mainWindow = Tk()
self.mainWindow.geometry("200x200")
self.mainWindow.title("My GUI Title")
self.mainWindow.protocol("WM_DELETE_WINDOW", self.quit)
#Label
lbCommand = Label(self.mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
#def run(self):
def start(self): #Start
self.mainWindow.mainloop()
#The Interface class contains methods that use attributes from itself and attributes from Process class.
def method1(self):
print(self.attrib1)
print(SecondThread.attrib2)
def quit(self):
if messagebox.askyesno('App','Are you sure you want to quit?'):
#In order to use quit function, mainWindow MUST BE an attribute of Interface.
self.mainWindow.destroy()
self.mainWindow.quit()
class Process(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.attrib2 = "Attrib from Process class"
def run(self):
global finish
while not finish:
print("Proceso infinito")
#Inside the infinite process a method from Interface class is used.
GUI.method1()
time.sleep(3)
finish = False
#Starts the GUI
GUI = Interface()
#Starts the infinity process
SecondThread = Process()
GUI.mainWindow.after(50, SecondThread.start)
#Waits until GUI is closed
GUI.start()
#GUI.join()
print("When GUI is closed this message appears")
#When GUI is closed we set finish to True, so SecondThread will be closed.
finish = True
#After all the program should finish but it raises the error: Tcl_AsyncDelete: async handler deleted by the wrong thread
I made a rough modification using garbage collection.
import time, threading, gc
from tkinter import *
from tkinter import messagebox
class Interface(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.attrib1 = "Attrib from Interface class"
def run(self):
#Main Window
self.mainWindow = Tk()
self.mainWindow.geometry("200x200")
self.mainWindow.title("My GUI Title")
self.mainWindow.protocol("WM_DELETE_WINDOW", self.quit)
#Label
lbCommand = Label(self.mainWindow, text="Hello world", font=("Courier New", 16)).place(x=20, y=20)
#Start
self.mainWindow.mainloop()
#The Interface class contains methods that use attributes from itself and attributes from Process class.
def method1(self):
print(self.attrib1)
print(SecondThread.attrib2)
def quit(self):
global finish
if messagebox.askyesno('App','Are you sure you want to quit?'):
#In order to use quit function, mainWindow MUST BE an attribute of Interface.
self.mainWindow.destroy()
del self.mainWindow
gc.collect()
finish = True
class Process(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.attrib2 = "Attrib from Process class"
def run(self):
global finish
while not finish:
print("Proceso infinito")
#Inside the infinite process a method from Interface class is used.
GUI.method1()
time.sleep(3)
finish = False
#Starts the GUI
GUI = Interface()
GUI.start()
#Starts the infinity process
SecondThread = Process()
SecondThread.start()
#Waits until GUI is closed
GUI.join()
#print("When GUI is closed this message appears")
#When GUI is closed we set finish to True, so SecondThread will be closed.
#finish = True