Tkinter: How to use threads to preventing main event loop from "freezing"
When you join the new thread in the main thread, it will wait until the thread finishes, so the GUI will block even though you are using multithreading.
If you want to place the logic portion in a different class, you can subclass Thread directly, and then start a new object of this class when you press the button. The constructor of this subclass of Thread can receive a Queue object and then you will be able to communicate it with the GUI part. So my suggestion is:
- Create a Queue object in the main thread
- Create a new thread with access to that queue
- Check periodically the queue in the main thread
Then you have to solve the problem of what happens if the user clicks two times the same button (it will spawn a new thread with each click), but you can fix it by disabling the start button and enabling it again after you call self.prog_bar.stop()
.
import queue
class GUI:
# ...
def tb_click(self):
self.progress()
self.prog_bar.start()
self.queue = queue.Queue()
ThreadedTask(self.queue).start()
self.master.after(100, self.process_queue)
def process_queue(self):
try:
msg = self.queue.get_nowait()
# Show result of the task if needed
self.prog_bar.stop()
except queue.Empty:
self.master.after(100, self.process_queue)
class ThreadedTask(threading.Thread):
def __init__(self, queue):
super().__init__()
self.queue = queue
def run(self):
time.sleep(5) # Simulate long running process
self.queue.put("Task finished")
Tkinter got freeze with threading
There are some unwritten rules about threading in combination with tkinter.
One of them is not to touch any widget outside of the thread of tkinter.
This line violates this rule.
label1.config(text = content)
In order to do this you can use a tkinter.StingVar
which isnt directly in the main event loop of tkinter.
The following line also violates the rule since it referece to the instance of tk.Tk().
win.after(100, set_text)
In addition, the call from inside the function to itself after
some time would create a loop that you already have built with a whileloop
. Its therefor unnecessary and wrong at the same time. Below you find a working exampel which has its limits. For another approache you can take a look at this or this.
from tkinter import *
import threading
win = Tk()
var = StringVar(value="this is a test on the left")
label1 = Label(win,textvariable=var)
label1.pack(side=LEFT)
label2 = Label(win, text="this is a test on the right")
label2.pack(side=RIGHT)
def set_text():
while(True):
content=input("let enter the substuition:")
var.set(content)
setTextthr=threading.Thread(target = set_text)
setTextthr.start()
win.mainloop()
How can I prevent a tkinter Gui from freezing while an async task is running?
An example with async-tkinter-loop library (written by me):
import asyncio
import tkinter as tk
import sys
from async_tkinter_loop import async_handler, async_mainloop
class pseudo_example():
def app(self):
self.root = tk.Tk()
self.root.minsize(100,100)
start_button = tk.Button(self.root, text="start", command=async_handler(self.await_fun))
start_button.pack()
self.testfield = tk.Label(self.root, text="test")
self.testfield.pack()
async_mainloop(self.root)
async def await_fun(self):
self.testfield["text"] = "start waiting"
await asyncio.sleep(2)
self.testfield["text"] = "end waiting"
if __name__ == '__main__':
gui = pseudo_example()
gui.app()
Threading In Tkinter To Stop Mainloop From Freezing
You don't need threads and you don't need a while loop for something this simple.
You already have an infinite loop running -- the event loop (eg: mainloop
). You can put work on the event queue to be executed in the future with the after
method. To perform an animation, just call a function to draw one frame, and have that function add itself back to the event queue to draw the next frame.
Here's a simple modification of your code to illustrate the point:
def bullets():
spryte1_pos = canvas.bbox(spryte1)
newpos1 = spryte1_pos[1]
newpos2 = spryte1_pos[2]-30
newpos3 = spryte1_pos[3]-20
spryte2 = canvas.create_rectangle(newpos1, newpos2, newpos3, newpos3, fill="blue")
shoot(spryte2, 10, 0)
def shoot(spryte, x, y):
canvas.move(spryte, x, y)
x0,y0,x1,y1 = canvas.bbox(spryte)
if x0 < 600
# call again in 100ms, unless the bullet is off the screen
canvas.after(100, shoot, spryte, x, y)
The canvas can probably handle several dozen sprytes being moved like this before it bogs down.
BTW, you can also remove all of the calls to Tk.update(window1)
in your move functions -- it adds overhead and slows the program down. As a general rule of thumb you should almost never call update
.
Why do i have to create my thread in another class, to avoid freezing my Tkinter UI?
Consider this line of code:
Thread(target=self.my_func()).start()
Functionally, it is exactly the same as this:
result = self.my_func()
Thread(target=result).start()
See the problem? You are immediately calling self.my_func()
in the current thread.
The target
needs to be a reference to a function:
Thread(target=self.my_func)
Call thread with loop out of gui without freezing gui
I could not figure out the solution I thought would work but I found this answer which I could adapt to my project and it now works as I wished.
Related Topics
How to Import a File in Python With Spaces in the Name
Finding the Max Value in a Two Dimensional Array
How to Write List Elements into a Tab-Separated File
Making a Matrix in Python 3 Without Numpy Using Inputs
Making a Dictionary from Each Line in a File
How to Read a Specific Line from a Text File in Python
Delete Every Non Utf-8 Symbols from String
Check If a Key Exists in a Bucket in S3 Using Boto3
Python How to Use Excelwriter to Write into an Existing Worksheet
How to Count the Amount of Sentences in a Paragraph in Python
Typeerror: the Json Object Must Be Str, Not 'Bytes'
How to Change Python Version in Command Prompt If I Have 2 Python Version Installed
Check If Value from One Dataframe Exists in Another Dataframe
How to Run Python Script from Another Machine Without Installing Imported Modules
Get the Row(S) Which Have the Max Value in Groups Using Groupby
Filtering the Dataframe Based on the Column Value of Another Dataframe