Tkinter: How to Use Threads to Preventing Main Event Loop from "Freezing"

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:

  1. Create a Queue object in the main thread
  2. Create a new thread with access to that queue
  3. 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



Leave a reply



Submit