0
votes

I have a worker class

class Parser: #(threading.Thread)
    ...
    def longTask(self,...)
        ...
        return
    ...

And a class for a window, which uses tkinter

class Window:
    ...
    def startProcess(self,...):
        p = Parser()
        t= threading.Thread(target=p.longTask())
        t.start()
        return
    ...

But even though longTask() is run in a separate thread, my GUI freezes - why?

My aim is to update a progressbar in Window while longTask() is running. longTask() internally updates a number, which ranges from 0 to 100.

Question: How can I accomplish this and prevent the GUI from freezing while longTask() is running?

Note: Apparently the thread doesnt start when I call t.start(), but when I initiate the thread by t= threading.Thread...

1
t is a instance resides on the stack, so after the function call, it will try call the release of that object, which is not possible. so you might set the thread as a member , something like self.t = threading.Thread(target=p.longTask())Cui Heng
I have included your suggestion, but why is longTask invoked on t=threading.Thread(... and not on t.start()?bogus
Should't that be p.longTask instead of p.longTask()Hans Then
Which is also the reason your p.longTask starts immediately before even creating the thread and your GUI freezes. Normally you just should pass a reference to your function to the Thread. But you invoke the function (making it start).Hans Then
changed it to self.t= threading.Thread(target= p.longTask, args=(myArg))bogus

1 Answers

0
votes

Although the solution(s) in the comments may work, I've found that in the past, threading has really disagreed with tkinter, which has its own virtual event manager. I wrote this so that if what you have tried doesn't work, you should be able to implement it this way.

import tkinter
import tkinter.ttk
import threading
import queue
import time

def long(steps):
    time.sleep(1)
    for i in range(20):
        steps.put(5)
        time.sleep(0.2)

class Window:

    def __init__(self):
        self.steps = queue.Queue()

        self.root = tkinter.Tk()
        self.root.title("Loading")
        self.progressbar = tkinter.ttk.Progressbar(self.root)
        self.progressbar.pack(fill="both", padx=10, pady=10)

    def update(self):
        if self.progressbar["value"] == 100:
            self.root.quit()
            self.root.destroy()
        elif not self.steps.empty():
            step = self.steps.get()
            self.progressbar["value"] += step  # Not progressbar.step because it loops
        self.root.after(100, self.update)

    def main(self):
        self.update()
        self.root.mainloop()


window = Window()
t = threading.Thread(target=long, args=(window.steps,))
t.start()
window.main()

It works better, when trying to have threaded objects interact with tkinter objects, to use some sort of mutable medium that can be used by both. Since the number of steps to increase the loading bar by can change, I used a Queue (It's also thread safe). The functions puts steps in the Queue every time something happens and the tkinter app has its own loop that checks to see if there are any new steps, effectively moderating the interaction between the thread and tkinter.

This is simply another way to prevent the GUI from freezing, not a way to fix the existing algorithm.