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

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.

Related

Python Multithreading - not running simultaneously

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

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.

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

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.

Tcl_AsyncDelete Error Multithreading Python

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

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