Understanding Tkinter window responsiveness when running a background thread - python

I am using Tkinter to create a GUI that is continually updated from a computation thread. In order to keep the GUI responsive, I create an event in each loop of this thread that tells the main thread (in which the Tkinter window was created) to update the GUI with the computed data. The structure of the thread looks like this:
class background_thread(threading.Thread):
def __init__(self, parent, stopper):
threading.Thread.__init__(self)
self.parent = parent
self.stopper = stopper
### Init. variables ###
def run(self):
while(not self.stopper.is_set()):
### Compute data ###
root.event_generate("<<update_gui>>", when = "now")
This works nicely, but I'm having trouble understanding the behavior of my Tkinter window when the thread is running. First, even though my mainloop() is still responsive (I get approximately 40 frames per second), the window itself is not responsive at all. By this, I mean that it's not really possible to drag it nor to close it. What is the reason for this and is there any way to prevent it?
One of the problems arising from this is that I have to stop the thread before closing the window. For this, I use a button that toggles its execution:
def callback_toggle_thread(self, event):
if(not self.background_thread.isAlive()):
self.background_thread.start()
else:
self.stopper.set()
But if I try to redefine the window close button protocole:
root.protocol("WM_DELETE_WINDOW", on_closing)
def on_closing():
if(self.background_thread.isAlive()):
self.stopper.set() # Asks the thread to stop
self.background_thread.join() # Waits for the thread to stop
root.destroy() # Closes the window
the program just freezes. Am I doing anything wrong?

As I suggested in a comment if you call root.update() after generating the event in the background thread it will force the application to update itself. This fixed the original issue although I can't walk through the specific flow of execution since the example code isn't enough to test.

Related

How do I Update Tkinter constantly without freezing everything else?

I need to send updates to tkinter from another thread, which should be handled immediately. Sadly Tkinter freezes whenever I try multithreading.
My Attempt:
I've read through several Tkinter-threaded pages but didn't find anything that works well, since most of them try creating a new thread on button.click(), which doesn't help here.
I tried not calling .mainloop() and instead calling the update functions myself, whenever an update comes in:
#GUI
def update(self, string):
self._interactor.config(text=string)
#interactor is a button
self._tk.update_idletasks()
self._tk.update()
This works fine until I use a loop with sleep() in the master to constantly update the text. GUI and master are frozen during sleep().
So I tried to use a threaded timer as discussed here.
#MASTER
gui=gui_remote(self)
def changetext():
text=self.gettextsomewhere()
self._gui.update(text)
loop=RepeatedTimer(5, changetext)
But this just leads to the following error, thrown by Tkinter:
RuntimeError: main thread is not in main loop
Having a hard time on how to solve this. Is it possible to call a GUI class on main thread and still have proper access to its functions?
How I got to this point:
For my project, I need a button, which represents several Buttons.
Every y (eg. 1.5) seconds, the displayed text should be updated to a new one from the outside.
Also, I want to keep GUI, Controller and Data separated, (using blueprint methods) so that later adjustments on each of them will be easier.
I already got it to work, using TK's .after() function, but I had to use GUI and controlling functions closely together.
My Plan
Have a GUI class, which is updateable from another object via simple public functions. The other object (the master) should be able to create a GUI object and call the GUI's update functions with new data every y seconds.
When the GUI button is clicked, simply call a certain method at master every time:
#GUI example
from tkinter import Tk, Button, Frame, Label
class gui_sample:
def __init__(self, master):
"""This is the very simple GUI"""
self._master=master
self._tk=Tk()
self._interactor= Button(self._tk, text="Apfelsaft", command=self._click)
self._interactor.pack()
self._tk.mainloop()
def update(self, string):
"""Handle interactor update"""
self._interactor.config(text=string)
def _click(self):
self._master.click()
#MASTER
from gui_module import *
class Controller:
def __init__(self):
self._gui=gui_sample(self)
self._run()
def _run(self):
#call this every 5 seconds
new_text=self.gettextfromsomewhere()
self._gui.update(new_text)
def click():
#do something
pass
#this code is just a blueprint it probably does nothing
My Problem:
I don't want the master to use TK functions since I might switch to another UI module later and keep the master's functionality. The master will constantly loop through what's being displayed next and needs to be accessible at the same time. Using loops with sleep() isn't a good idea since they will block both, the master and the GUI. Calling .mainloop() is also problematic since it will block all other programs. The gui should always respond to updates and not ask for them.

How to easily avoid Tkinter freezing?

I developed a simple Python application doing some stuff, then I decided to add a simple GUI using Tkinter.
The problem is that, while the main function is doing its stuff, the window freezes.
I know it's a common problem and I've already read that I should use multithreads (very complicated, because the function updates the GUI too) or divide my code in different function, each one working for a little time.
Anyway I don't want to change my code for such a stupid application.
My question is: is it possible there's no easy way to update my Tkinter window every second? I just want to apply the KISS rule!
I'll give you a pseudo code example below that I tried but didn't work:
class Gui:
[...]#costructor and other stuff
def refresh(self):
self.root.update()
self.root.after(1000,self.refresh)
def start(self):
self.refresh()
doingALotOfStuff()
#outside
GUI = Gui(Tk())
GUI.mainloop()
It simply will execute refresh only once, and I cannot understand why.
Thanks a lot for your help.
Tkinter is in a mainloop. Which basically means it's constantly refreshing the window, waiting for buttons to be clicked, words to be typed, running callbacks, etc. When you run some code on the same thread that mainloop is on, then nothing else is going to perform on the mainloop until that section of code is done. A very simple workaround is spawning a long running process onto a separate thread. This will still be able to communicate with Tkinter and update it's GUI (for the most part).
Here's a simple example that doesn't drastically modify your psuedo code:
import threading
class Gui:
[...]#costructor and other stuff
def refresh(self):
self.root.update()
self.root.after(1000,self.refresh)
def start(self):
self.refresh()
threading.Thread(target=doingALotOfStuff).start()
#outside
GUI = Gui(Tk())
GUI.mainloop()
This answer goes into some detail on mainloop and how it blocks your code.
Here's another approach that goes over starting the GUI on it's own thread and then running different code after.
'Not responding' problem can be avoided using Multithreading in python using the thread module.
If you've defined any function, say combine() due to which the Tkinter window is freezing, then make another function to start combine() in background as shown below:
import threading
def combine():
...
def start_combine_in_bg():
threading.Thread(target=combine).start()
Now instead of calling combine(), you have to call start_combine_in_bg()
I did this and now my window is not freezing so I shared it here. Remember you can run only one thread at a time.
Have a look for reference: stackoverflow - avoid freezing of tkinter window using one line multithreading code
Here's how I managed to work around this issue (maybe it's quite dirty I don't know):
Whenever I update my tkinter window through the doingALotOfStuff() function, I call myTk.update().
It avoids freezing for me.

Python Tkinter and NetworkX freezes after moving mainloop to own Thread

I'm making a simple window to hold my NetworkX graph, and I want this GUI component to be separate from my logic. So what I'm trying to achieve is to make a function that makes the window, initializes the graph, starts the mainloop in a Thread and returns the gui reference. I've done this exact thing without networkX and canvas involved, and it works great, but now this doesn't work for some reason. What I have is:
def getNewGraphWindow():
root = Tk()
app = GraphUI(root)
root.mainloop()
#mainThread = Thread(target=root.mainloop)
#mainThread.start()
return app
So this code works perfectly, it creates the window, draws the graph and all that, but obviously I don't get the "app" reference cause its stuck in the mainloop. But if I replace that code with the 2 commented out lines, to simply run root.mainloop() in its own thread, all the code runs, but the window doesn't respond and the graph is not drawn. What's wrong?
You have to run the mainloop in the thread where you created the Tcl interpreter, according to the _tkinter source code:
The Tcl interpreter is only valid in the thread that created it, and
all Tk activity must happen in this thread, also. That means that the
mainloop must be invoked in the thread that created the interpreter.
So you'll need to run the mainloop in the main thread, and do whatever else you need to do in a background thread. You actually might be able to run the mainloop in a background thread, as long as you also create the Tk element in that same thread.

Python / Pyside Code not executing sequentially?

I am building a Qt-based GUI using Pyside. Inside a specific class who has access to a QMainWindow (_theMainWindow) class which in turn has access to 2 other Qt Widgets (theScan & theScanProgress) I am trying to show() the last one by executing
def initiateScan(self):
self._theMainWindow.theScan.theScanProgress.show()
This works just fine, the theScanProgress widget appears.
However, when I add the line that makes the application sleep (and a print statement), as below
def initiateScan(self):
self._theMainWindow.theScan.theScanProgress.show()
print("test")
time.sleep(3)
the program seems to go to sleep BEFORE the widget appears, i.e. as if the time.sleep(3) gets executed before self._theMainWindow.theScan.theScanProgress.show()
Any ideas why this happens?
This is because of the main loop which processes gui events. If you are not using threads you can only execute one function at a time. I strongly suspect that show emits a signal which goes into the event queue, which is in turn blocked until the current function returns.
Put another way, Qt is event driven, and it can only do one event at a time. What ever you did call initiateScan added an event to the stack that was to execute the function (something like you pushed a button, which emitted a signal, which then triggered the function), and that function can do some computation, alter internal state of your objects, and add events to the stack. What show does underneath is emit a signal to all of it's children for them to show them selves. For that code to run, it has to wait for the current event (the function with your sleep) to return. During the sleep the entire gui will be unresponsive for the exact same reason.
[I probably butchered some of the terms of art]
show only schedules the appearance of the progress widget. But since you are blocking the main thread with your sleep, the main thread cannot perform the scheduled action until you release it.
You have to use threads or find another way to wait 3 seconds.

Closing threaded Tkinter window

I use a Tkinter window to visualize some output of my programm. The window is threaded (see basic structure below) and basically it works quite fine. So far, I only have trouble closing the window. When I clicke the "X" button for closing the window it works.
However, when I call the Monitor.close() method from the main programm that starts the monitor thread, the window just freezes (e.g., it doesn't react on clicking the "X" button) and the thread monitor keeps running. Thus, the main program does not exit.
So, at the moment, I also have to close first the window "manually" by clicking the closing button and then the main program. Not a big issue, but it would be great, if the main program could close the window by itself. Any hints?
Thanks and best regards,
Christian
class Monitor(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.start()
def close(self):
self.root.quit()
self.root.destroy()
def run(self):
self.root=Tkinter.Tk()
self.root.protocol("WM_DELETE_WINDOW", self.close)
self.root.mainloop()
Python Threading and Tk(inter) used in this way do not mix well, as they violate the Tcl/Tk threading model of using Tk just from one thread.
It works great with message passing though, just not with direct calls from a thread. So you need to add some message passing via Queue to this.
Have a look at http://effbot.org/zone/tkinter-threads.htm for an example.

Categories