Access to Tkinter's Text widget by background thread cause crashes - python

I created a simple tkinter app, where have used two threads. Their task is to write numbers to widgets such as label and text. One thread is triggered by button (click event) and second is executed as a background thread.
import Tkinter as tk
from ttk import *
from Tkconstants import *
import threading, thread, time
def tl1(text,counter):
while True:
text.insert(END,counter)
counter += 1
time.sleep(2)
def tl2(label,counter):
while True:
label['text'] = counter
counter += 1
time.sleep(1)
class mainWindow():
def __init__(self, master):
self.master = master
self._initLayout()
def _initLayout(self):
#button
self.button = tk.Button(self.master, text="thread1_start", command = self._task1)
self.button.pack()
#label
self.label = tk.Label(self.master)
self.label.pack()
#text
self.text = tk.Text(self.master, width=30)
self.text.pack()
def _task1(self):
t1 = thread.start_new_thread(tl1,(self.text,1))
def _task2(self):
t2 = thread.start_new_thread(tl2,(self.label,1000))
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.mainWindow = mainWindow(self)
self.mainWindow._task2() #background_thread
app = App()
app.mainloop()
In this manner everything works fine, but if we change the background thread to display results on text widget, the whole app freezes.
Why background thread works fine communicating with label but causes problems with text widget? Is there any way to run it properly?

Tkinter isn't thread safe. You can only access widgets from the thread that created them. Your threads will need to put data on a thread-safe queue, and your GUI thread will need to poll the queue.
In your particular case you don't need threads at all. You can use the tkinter after method to run code periodically.

Related

Posting Thread Progress back to tkinter Frame

I am posting this as informational for folks looking for a way to communicate thread progress back to a tkinter Frame or window. I have seen several approaches detailed in SO and other sites, but none really seemed adequate to me. So here is an approach that displays progress as both a message box update and advancing a Scale Widget. It uses the tkinter variable classes StringVar and DoubleVar rather than trying to use callbacks or continuously poll a queue in the main thread.
Comments are, of course, welcome, but it appears this approach works well.
`
import tkinter as tk
from tkinter import ttk, END, NW, GROOVE
import threading
import queue
import time
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.queue = queue.Queue()
self.msgCt=0
self.listbox = tk.Listbox(self, width=20, height=5)
self.scaleVal=tk.DoubleVar()
self.progressbar = ttk.Scale(self, orient='horizontal',
length=300,
from_=0.0, to=100.0,
variable=self.scaleVal)
self.scaleVal.set(0.0)
self.button = tk.Button(self, text="Start", command=self.spawnthread)
self.msgBtn = tk.Button(self,text="Set Msg", command=self.sendMessage)
self.msgTxt=tk.StringVar(self,"Messages Here...")
self.msgBox=tk.Message(self,textvariable=self.msgTxt,width=200,
anchor=NW,relief=GROOVE)
self.listbox.grid(row=0,column=0,columnspan=2)
self.msgBox.grid(row=1,column=0,columnspan=2)
self.progressbar.grid(row=2,column=0,columnspan=2)
self.button.grid(row=3,column=0)
self.msgBtn.grid(row=3,column=1)
def spawnthread(self):
self.button.config(state="disabled")
self.listbox.delete(0, END)
self.thread = ThreadedClient(self.queue,self.msgTxt,self.scaleVal)
self.thread.start()
self.periodiccall()
def sendMessage(self,msg=None):
if not msg==None:
self.msgTxt.set(msg)
else:
self.msgTxt.set("Message {}".format(self.msgCt))
self.msgCt+=1
def periodiccall(self):
self.checkqueue()
if self.thread.is_alive():
self.after(100, self.periodiccall)
else:
self.button.config(state="active")
def checkqueue(self):
while self.queue.qsize():
try:
msg = self.queue.get(0)
self.listbox.insert('end', msg)
# self.progressbar.step(25)
except queue.Empty:
pass
class ThreadedClient(threading.Thread):
def __init__(self, qu, mtxt,dvar):
threading.Thread.__init__(self)
self.queue = qu
self.msgTxt=mtxt
self.scaleVal=dvar
def run(self):
self.scaleVal.set(0.0)
for x in range(1, 10):
time.sleep(2)
msg = "Function %s finished..." % x
self.msgTxt.set(msg)
self.scaleVal.set(x*10)
self.queue.put(msg)
if __name__ == "__main__":
app = App()
app.mainloop()
`
In response to the several comments, here is a new version of the code:
import tkinter as tk
from tkinter import ttk, END, NW, GROOVE
import threading
import queue
import time
from pickle import FALSE
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.msgCt=0
self.thrdCt=0;
self.scaleVal=tk.DoubleVar()
self.doneSignal=tk.BooleanVar()
self.doneSignal.set(False)
self.doneSignal.trace("w",self.on_doneSignal_set)
self.progressbar = ttk.Progressbar(self, orient='horizontal',
length=300,
maximum=100.0,
variable=self.scaleVal)
self.scaleVal.set(0.0)
self.startBtn = tk.Button(self, text="Start", command=self.spawnthread)
self.stopBtn=tk.Button(self,text="Stop", command=self.stopthread)
self.stopBtn.config(state="disabled")
self.msgBtn = tk.Button(self,text="Set Msg", command=self.sendMessage)
self.msgTxt=tk.StringVar(self,"Messages Here...")
self.msgBox=tk.Message(self,textvariable=self.msgTxt,width=200,
anchor=NW,relief=GROOVE)
self.msgBox.grid(row=0,column=0,columnspan=3)
self.progressbar.grid(row=1,column=0,columnspan=3)
self.startBtn.grid(row=2,column=0)
self.stopBtn.grid(row=2,column=1)
self.msgBtn.grid(row=2,column=2)
def on_doneSignal_set(self,*kwargs):
self.sendMessage("Thread is DONE")
self.startBtn.config(state="active")
self.stopBtn.config(state="disabled")
def stopthread(self):
if self.thread.is_alive():
self.thread.stopNow()
def spawnthread(self):
self.thrdCt=0
self.startBtn.config(state="disabled")
self.stopBtn.config(state="active")
self.thread = ThreadedClient(self.msgTxt,self.scaleVal,self.doneSignal)
self.thread.start()
# self.periodiccall()
def sendMessage(self,msg=None):
if not msg==None:
self.msgTxt.set(msg)
else:
self.msgTxt.set("Message {}".format(self.msgCt))
self.msgCt+=1
class ThreadedClient(threading.Thread):
def __init__(self, mtxt,dvar,dsig):
threading.Thread.__init__(self)
self.msgTxt=mtxt
self.scaleVal=dvar
self._stopNow=False
self._doneSignal=dsig
self._lock=threading.Lock()
def run(self):
self._stopNow=False
self.scaleVal.set(0.0)
for x in range(1, 10):
if not self.checkStopNow():
time.sleep(2)
msg = "Function %s finished..." % x
self.msgTxt.set(msg)
self.scaleVal.set(x*10)
else:
break
self._doneSignal.set(True)
def stopNow(self):
with self._lock:
self._stopNow=True
def checkStopNow(self):
rtrn=False
with self._lock:
rtrn=self._stopNow
return rtrn
if __name__ == "__main__":
app = App()
app.mainloop()
First, the motivation for this exercise in the first place: I have a large python application that uses Scipy.optimize to find a solution to a modeling problem. This can often take a long time, so I wanted a way to have it run, but post messages to the user periodically to let them know things are happening, allow user to abort in the middle, and finally to post a message to the main thread that the modeling is now done. My original code was partly based on a threading example that assumed the producer/consumer model of threading, whereby a producer creates data (put into a Queue) and the consumer consumes it. This is the WRONG model for my problem, so this new code has no Queue. It just simulates a long modeling process using the run() method, in which there are calls to sleep() which could obviously be replaced by steps in the SciPy.minimize function calls.
This new example uses DoubleVar to allow the thread to update a progressBar (thanks to stovfl for that suggestion), a StringVar to update a message box in the main thread from the modeling thread, and finally a BooleanVar to signal the main thread that things are done. This version has no polling in the main thread. To me, that does not seem a very elegant solution!
How do I know the changes to the DoubleVar, StringVar and BooleanVar get through to the main thread? Only that this program works!! Note that the message box can be updated from either the modeling thread or using a button in the main GUI thread.
Again, comments welcome -- Give me reasons this should NOT work, then tell me why it does given those reasons! Does this violate some basic design of Python, or would there be a situation where this would not work for some reason??

multithreading from a tkinter app

I have a tkinter application that runs on the main thread. After receiving some input from the user, a new thread is created to perform functions in a separate class. The main thread continues to a Toplevel progress window.
My question is, how can I communicate to the main thread when the second thread has finished its tasks, in order to close the progress window?
import tkinter as tk
from tkinter import ttk
from threading import Thread
import time
class Application:
def __init__(self, master):
# set main window
frame = tk.Frame(master, width=300, height=100)
frame.pack(fill=tk.BOTH)
# button widget
run_button = tk.Button(frame, text="GO", command=self.do_something)
run_button.pack()
# simulate some gui input from user
self.user_input = "specified by user"
def do_something(self):
thread1 = Thread(target=FunctionClass, args=(self.user_input,))
thread1.start() # launch thread
ProgressWindow() # main thread continues to new tkinter window
class ProgressWindow(tk.Toplevel):
""" displays progress """
def __init__(self):
super().__init__()
self.progress = ttk.Progressbar(
self, orient="horizontal", length=300, mode="indeterminate")
self.progress.pack()
self.note = "Processing data..."
self.p_label = tk.Label(self, text=self.note)
self.p_label.pack()
self.progress.start(50)
class FunctionClass:
""" thread1 works on this class """
def __init__(self, user_data):
self.user_data = user_data
self.do_something_else()
def do_something_else(self):
# simulate thread 1 working
time.sleep(3)
print("Thread1 job done")
if __name__ == "__main__":
root = tk.Tk()
Application(root)
root.mainloop()
* UPDATE *
tkinter's event_generate as suggested by iCart works well. See code update below.
import tkinter as tk
from tkinter import ttk, messagebox
from threading import Thread
import time
class Application:
def __init__(self, master):
# set main window
frame = tk.Frame(master, width=300, height=100)
frame.pack(fill=tk.BOTH)
# button widget
run_button = tk.Button(frame, text="GO", command=self.do_something)
run_button.pack()
# simulate some gui input from user
self.user_input = "specified by user"
def do_something(self):
thread1 = Thread(target=FunctionClass, args=(self.user_input,))
thread1.start() # launch thread
ProgressWindow() # main thread continues to new tkinter window
class ProgressWindow(tk.Toplevel):
""" displays progress """
def __init__(self):
super().__init__()
self.progress = ttk.Progressbar(self, orient="horizontal", length=300, mode="indeterminate")
self.progress.pack()
self.note = "Processing data..."
self.p_label = tk.Label(self, text=self.note)
self.p_label.pack()
self.progress.start(50)
class FunctionClass:
""" thread1 works on this class """
def __init__(self, user_data):
self.user_data = user_data
self.do_something_else()
def do_something_else(self):
# simulate thread 1 working
time.sleep(3)
print("Thread1 job done")
# call <<stop>> virtual event (unsure if 'tail' is necessary here)
root.event_generate("<<stop>>", when="tail")
def end_program(*args):
""" called with tkinter event_generate command after thread completion """
messagebox.showinfo("Complete", "Processing Complete")
root.destroy()
if __name__ == "__main__":
root = tk.Tk()
Application(root)
root.bind("<<stop>>", end_program) # bind to event
root.mainloop()
I'm not too familiar with tkinter so i can't show you ze code, but you should look at tkinter's event system, particularly firing custom events.
This question may help you get started
You can use Queue, from Queue module.
def get_queue(self, queue):
status = queue.get()
if status:
self.do_your_action()
self.master.after(1000, self.get_queue)
And in the progress bar window, You pass queue and put something in it when progress bar finishes.

Tkinter Show splash screen and hide main screen until __init__ has finished

I have a main tkinter window that can take up to a few seconds to load properly. Because of this, I wish to have a splash screen that shows until the init method of the main class has finished, and the main tkinter application can be shown. How can this be achieved?
Splash screen code:
from Tkinter import *
from PIL import Image, ImageTk
import ttk
class DemoSplashScreen:
def __init__(self, parent):
self.parent = parent
self.aturSplash()
self.aturWindow()
def aturSplash(self):
self.gambar = Image.open('../output5.png')
self.imgSplash = ImageTk.PhotoImage(self.gambar)
def aturWindow(self):
lebar, tinggi = self.gambar.size
setengahLebar = (self.parent.winfo_screenwidth()-lebar)//2
setengahTinggi = (self.parent.winfo_screenheight()-tinggi)//2
self.parent.geometry("%ix%i+%i+%i" %(lebar, tinggi, setengahLebar,setengahTinggi))
Label(self.parent, image=self.imgSplash).pack()
if __name__ == '__main__':
root = Tk()
root.overrideredirect(True)
progressbar = ttk.Progressbar(orient=HORIZONTAL, length=10000, mode='determinate')
progressbar.pack(side="bottom")
app = DemoSplashScreen(root)
progressbar.start()
root.after(6010, root.destroy)
root.mainloop()
Main tkinter window minimum working example:
import tkinter as tk
root = tk.Tk()
class Controller(tk.Frame):
def __init__(self, parent):
'''Initialises basic variables and GUI elements.'''
frame = tk.Frame.__init__(self, parent,relief=tk.GROOVE,width=100,height=100,bd=1)
control = Controller(root)
control.pack()
root.mainloop()
EDIT: I can use the main window until it has finished loading using the .withdraw() and .deiconify() methods. However my problem is that I cannot find a way to have the splash screen running in the period between these two method calls.
a simple example for python3:
#!python3
import tkinter as tk
import time
class Splash(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.title("Splash")
## required to make window show before the program gets to the mainloop
self.update()
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.withdraw()
splash = Splash(self)
## setup stuff goes here
self.title("Main Window")
## simulate a delay while loading
time.sleep(6)
## finished loading so destroy splash
splash.destroy()
## show window again
self.deiconify()
if __name__ == "__main__":
app = App()
app.mainloop()
one of the reasons things like this are difficult in tkinter is that windows are only updated when the program isn't running particular functions and so reaches the mainloop. for simple things like this you can use the update or update_idletasks commands to make it show/update, however if the delay is too long then on windows the window can become "unresponsive"
one way around this is to put multiple update or update_idletasks command throughout your loading routine, or alternatively use threading.
however if you use threading i would suggest that instead of putting the splash into its own thread (probably easier to implement) you would be better served putting the loading tasks into its own thread, keeping worker threads and GUI threads separate, as this tends to give a smoother user experience.

Python: A GUI that runs alongside a program as a controller [duplicate]

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.

Tkinter freezing with multi threading

I need an application that basically runs a progress bar for a few seconds, and then closes itself down. I used this as an example whilst adapting it first for Python 3.4 and then for my own application. However, due to the way I've structured my code, it will first run the thread and it's tasks to it's completion and only then display the programme. This is very problematic for me, and I don't see a way around it when using classes.
from tkinter import ttk as ttk
from tkinter import *
import threading
import time
class App:
def afterLoading(self):
print('Loading finished')
def process(self,master):
time.sleep(2)
print('Thread Done')
self.afterLoading()
def __init__(self, master):
print()
master.geometry("1270x800")
master.resizable(0,0)
t1 = threading.Thread(target=self.process, args=(master,))
t1.start()
self.loadingFrame(master)
t1.join()
def loadingFrame(self, master):
frame = Frame(master, width=500, height=300)
frame.pack(side=BOTTOM, pady=50)
self.bar = ttk.Progressbar(frame, orient='horizontal', mode = 'indeterminate')
self.bar.pack(fill=BOTH)
self.bar.start(50)
self.loadingLabel = Label(frame, text="Please wait whilst the programme initializes.")
self.loadingLabel.pack()
root = Tk()
b = App(root)
root.mainloop()
Well, with your example code, you can just remove the call to t1.join() to get the behavior you want. That way, you'll be able to start the Tk event loop immediately after starting the background thread, which means your GUI can actually start up while the thread runs in the background. Using the t1.join() call prevents root.mainloop() from executing until the thread is complete, which means your GUI won't display until the thread is complete, either.

Categories

Resources