Reputation: 127
I am trying to implement Multithreading while using a GUI in tkinter. I came along this solution, but i am failing to implement it.
So basically i need to know:
How do i need to alter my Code to make the Progressbar interact nice and smoothly, i.e. not going in unresponsive mode when the GUI loses focus?
This is my code boiled down:
from tkinter import *
import queue
import threading
from tkinter.ttk import *
class ThreadedTask(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
# Gui class
class MyGui(Frame):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.queue = queue.Queue()
self.init_ui()
# define a button to fulfill long task
def init_ui(self):
self.frame = Frame(self, relief=RAISED, borderwidth=1)
self.frame.grid(row=0, column=0, columnspan=1, sticky='ns')
self.status_frame = Frame(self, relief=RAISED, borderwidth=1, height=20)
self.status_frame.grid(row=1, column=0, columnspan=3, sticky='nesw')
self.status_frame.grid_configure(padx=3, pady=3)
self.button = Button(self.frame, text='do Stuff', command=self.do_stuff)
self.button.grid(padx=3, pady=3)
self.progress = Progressbar(self.status_frame, orient="horizontal", length=80, mode="determinate")
self.progress.grid(row=1, column=0, pady=3, sticky='nesw')
self.grid()
# start ThreadedTask here
def do_stuff(self):
ThreadedTask(self.queue).start()
self.queue_process(DoStuffClass(ui=self))
def queue_process(self, process, retry_time=10):
self.master.after(retry_time, process)
def update_status(self):
self.parent.update_idletasks()
class DoStuffClass:
def __init__(self, ui=None):
self.ui = ui
self.long_task()
def long_task(self):
# do stuff here and update the progressbar from MyGui
self.ui.progress['maximum'] = 10000
# some lengthy task ...
for i in range(10000):
print(i)
self.ui.progress.step()
self.ui.parent.update_idletasks()
# main
root = Tk()
root.geometry("150x80+50+50")
MyGui(root)
root.mainloop()
root.quit()
I think right now my problem is the wrong implementation of queues and Threading, i.e. self.queue_process(DoStuffClass(ui=self))
... the behaviour is the same as if i wouldn't use a queue and Multithread at all. The progressbar works, as long as it stays "in focus", which means me not clicking anything else on the desktop. As soon as i'm clicking elsewhere on the desktop and the GUI loses focus, the GUI goes in "unresponsive" mode and the progress bar does not update anymore. Also, sometimes Tcl closes the wrong Thread, which makes the whole program crash.
Upvotes: 2
Views: 746
Reputation: 127
So after a couple of tries i figured out what to do:
from tkinter import *
import queue
import threading
from tkinter.ttk import *
class ThreadedTask(object):
def __init__(self, parent):
self.parent = parent
self.queue = queue.Queue()
self.gui = MyGui(parent, self.queue)
self.work_queue()
def work_queue(self):
""" Check every 100 ms if there is something new in the queue. """
try:
self.parent.after(200, self.work_queue)
print('working queue with task {}...'.format(self.queue.get_nowait()))
except queue.Empty:
pass
# Gui class
class MyGui(Frame):
def __init__(self, parent, queue):
super().__init__(parent)
self.parent = parent
self.queue = queue
self.init_ui()
# define a button to fulfill long task
def init_ui(self):
self.frame = Frame(self, relief=RAISED, borderwidth=1)
self.frame.grid(row=0, column=0, columnspan=1, sticky='ns')
self.status_frame = Frame(self, relief=RAISED, borderwidth=1, height=20)
self.status_frame.grid(row=1, column=0, columnspan=3, sticky='nesw')
self.status_frame.grid_configure(padx=3, pady=3)
self.button = Button(self.frame, text='do Stuff', command=self.init_button_loop)
self.button.grid(padx=3, pady=3)
self.progress = Progressbar(self.status_frame, orient="horizontal", length=80, mode="determinate")
self.progress.grid(row=1, column=0, pady=3, sticky='nesw')
self.grid()
def start_thread(self, function_name, queue):
t = threading.Thread(target=function_name, args=(queue,))
# close thread automatically after finishing task
t.setDaemon(True)
t.start()
# execute button push by spawning a new thread
def init_button_loop(self):
self.start_thread(self.exec_button_loop, self.queue)
# execute long task
def exec_button_loop(self, queue):
self.progress['maximum'] = 10000
for i in range(10000):
# update progressbar
queue.put(self.progress.step())
# main
root = Tk()
root.geometry("150x80+50+50")
client = ThreadedTask(root)
root.mainloop()
The Difficulty was to figure out how to interact with queues and threads, while pressing buttons in the gui.
Basically my fault was to declare the queue in the wrong class and the wrong use of the after
function while not knowing where to start the threads.
The new implementation follows the principle of filling the queue up in a new thread, which is spawned when pressing the gui button. The queue is periodically checked from the main thread whether theres something to do. This prevents unresponsiveness due to a safe communication between gui and mainthread.
Upvotes: 1