Tkinter, threads, several widgets to update periodically - python

I am trying to do an app using tkinter that gets information from some internet sources after entering stuff in an input box and then clicking a "load" button.
The button function reads the stuff string from the input box. After its loaded it retrieves some info from the internet (which is blocking), and then update some labels with this information.
Obviously, when clicking the load button, the app freezes a micro second due to the requests blocking the flow of the program.
Once the info is retrieved, and the labels updated, some other labels would need to keep retrieving data from the internet constantly. To do this I have them done a bit like this:
Note: the print statements are done for testing, so I can see them on the console
def update_price_label(self):
# TODO fix .after() duplicate
print("Updating stuff")
price = self.get_formatted_price(self.stuff) # this is another function being called, passing an argument of the stuff that has been loaded by the load button, this function returns price
self.PriceValue.configure(text=price) # updates the price label with the price obtained from the function above
self.PriceValue.after(1000, self.update_price_label) # sets a scheduler to run this function to update the price label each second
Above there is a function that is called upon clicking "load" for a label that needs to be updated all the time, this function calls another function that receives an argument, and then returns the price. Then the label is updated, and then its scheduled in an endless loop using the after() method of the priceValue label control. This way, the price gets updated all the time
def get_formatted__price(self, stuff):
price = RETRIEVE PRICE # this is not a real function, but this is the request being done to the server to get the price
return f"{price:.12f} # sets decimal numbers
This function is called by update_price_label(), receives an argument and returns the price
As you can see I have divided the label update functions vs the actual functions that actually retrieve the info from a server. So the first function is responsible for calling another function to retrieve the information, update the label, and then reschedule itself again using the after() method of each label widget.
There are like 5 functions like this that need to update several labels on the app, connecting to different sources and keeping the information up to date. All of them are scheduled using after() and run on the same intervals (1 second).
Obviously, due to not using any threading, the app freezes a lot when information is being requested due to them being blocking in nature.
So I need to implement threading, or any form of concurrency. And I am unable to find any good tutorials on this, or at least that fits my needs for an app that fetches information from sources on a regular basis.
I am still grasping the concepts of threading and concurrency in general, and maybe there are other ways such as asynchronism or other methods of concurrency that I don't know yet and might be better suited. But Threading seems to be the one that seems to be mostly used with Tkinter.
I assume each of these request functions would need a thread. Like this:
get_formatted_price_thread = Thread(target=get_formatted_price, args=(stuff), daemon=True) # calling a thread with an argument on the function and setting daemon to true, so it closes when I close the app
So I have tried to create threads on one of them as an example and I have found a few limitations such as:
There's no way to get the returned value of the get_formatted_price() function directly. So the other way could be just letting the function in a thread to change the label value. Or wrapping the whole label update function in a thread. But as I read everywhere, Tkinter is not thread safe. Meaning that updating tkinter widgets may work fine on some operative systems but not in others.
Besides that, I seem to struggle on to how to turn the structure of this app to something that works well with threading or any kind of concurrency. As I need to have all the labels updated as soon as new info is retrieved.
My other concern is that threads are controlled by the operative system. As it is the one that decides when threads are launched, and how that could impact my apps performance when fetching data.
I have also checked queues and the Queue library, but I am not sure if this what would help me, as each price update would be put into the queue by the thread, and retrieved by the label widget. But the information could be outdated as the queue gets the first element of the queue.
So my question here is what would I need to change here to achieve what I need. And if threading is what I need to go on, or if maybe I would need to try another approach.
Any example source code of apps that do what I need to would be very appreciated. After all, retrieving information, and keeping widgets up to date with that information should be like a pretty common use case.
Another approach I have thought of is creating a data structure, such as a Python dictionary, or an object. Each server fetching function would run in a thread in an endless loop as a daemon, and would write to the dictionary. Then the label widget update functions, since they are scheduled, would read the data on the dictionary and update the labels accordingly. But I think this approach might be messy, and probably there would be a delay on updating the labels vs the information on the dictionaries, unless a smaller after() scheduler timer is set. Or maybe all solutions are messy by default
Thank you.

I would solve this by creating a data structure, creating a function that can update the display based on the current values in the data structure, and then binding to an event that calls this function. Then, create a thread that updates this data structure and emits the event when the data changes.
Here's a contrived example that calls a web service once a second and updates a simple data structure with the time and timezone information. Whenever the data changes it emits a <<Tick>> event that triggers an update of the display.
I'm not an expert on writing threaded tkinter code, and it's my understanding that except in a very few circumstances it is unsafe to run any tkinter code in a thread other than the one where the widgets were created. One exception is that it's safe for additional threads to generate events, since the events themselves get handled in the main GUI thread. I'm guessing it's also safe to call the winfo_exists function since it doesn't modify any internal data structures.
This example kills itself after 10 seconds so as to not hammer the server for too long.
import requests
import tkinter as tk
from tkinter.font import Font
from threading import Thread
import time
class ThreadedClock(tk.Frame):
data = {"time": "", "tz": ""}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.time_label = tk.Label(self, width=12, font=Font(size=32))
self.tz_label = tk.Label(self, text="GMT")
self.time_label.pack(side="top", fill="x")
self.tz_label.pack(side="top", fill="x")
# call the refresh function on every <<Tick>> event
self.bind("<<Tick>>", self.refresh)
# start a thread to update the data and generate <<Tick>> events
self.thread = Thread(target=self.get_data, daemon=True)
self.running = True
self.thread.start()
def get_data(self):
while self.winfo_exists():
now = time.time()
response = requests.get(
"https://timeapi.io/api/Time/current/zone?timeZone=GMT"
)
t = response.json()
timestr = f"{t['hour']:02}:{t['minute']:02}:{t['seconds']:02}"
self.data = {"time": timestr, "tz": t["timeZone"]}
self.event_generate("<<Tick>>")
delta = time.time() - now
time.sleep(delta)
def refresh(self, event=None):
self.time_label.configure(text=self.data["time"])
self.tz_label.configure(text=f"timezone: {self.data['tz']}")
if __name__ == "__main__":
root = tk.Tk()
root.after(10000, root.destroy)
clock = ThreadedClock(root)
clock.pack(fill="both", expand=True)
root.mainloop()

Related

Reduce CPU usage in Pywin32 system tray app main loop

I built a small system tray app based on Simon Brunning's example.
According to How can I use win32gui in order to have an app run in the background?,
calling win32gui.PumpMessages() runs a loop by itself, but I need to make an HTTP request every ~15 min and if necessary update the system tray icon and tooltip.
I do this by creating a separate process with an infinite loop, sleeping for 900 s after every request, which updates a dictionary shared between the two (main app, requesting) processes. The main app runs an infinite loop checking the shared dictionary for updates and win32gui.PumpWaitingMessages() on every cycle, which I would like to keep so I can get a tooltip immediately on hovering or the context menu on clicking.
This is a simplified version of the relevant part of the code, with hopefully sufficient detail to show how it works:
from multiprocessing import Process, Manager
def get_data(data):
while True:
new_data = make_request()
data["value"] = new_data
data["refresh"] = True
sleep(900)
class SysTrayIcon():
def __init__():
self.data = Manager().dict({"value": "foo", "refresh": False})
self.update_process(target=get_data, args=(self.data))
self.update_process.start()
while True:
if self.data["refresh"]:
self.refresh_icon()
self.data["refresh"] = False
win32gui.PumpWaitingMessages()
I see a pretty high CPU load (~10%) which is probably due to the loop running very quickly and checking self.data["refresh"] repeatedly.
What would be an elegant way to avoid this and/or reduce CPU load? I could probably just add a simple sleep(0.03) on every iteration which would still give me a sufficiently quick response for interactions with the app, but maybe there is a better solution?

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.

wxpython: How to pass XY coordinator and draw it out?

I'm new for python but willing to learn. I have a set of hardware to receive touch coordinators and draw line accordingly to coordinators.
My problem is that the wxpython won't draw line if coordinator changes.
Here is my code : https://github.com/eleghostliu/homework/blob/master/DrawXY_byWxPython/PythonApplication1/PythonApplication1.py
can someone give advise, thanks.
You registered for EVT_PAINT, yet you are not triggering the event as the data changes. The frame has no idea whether data changed or not, unless you specifically inform it.
You can trigger the event simply by calling
frame.Refresh()
You can hook it in several ways. For instance, you could pass frame.Refresh bound method as a parameter to MainProcess so that it can make the function call to refresh the frame. Something like the following:
WARNING: Erroneous code piece
# Start a socket server
def MainProcess(refresh_callback):
while True:
refresh_callback()
***********************************************
frame = DrawPanel()
frame.Show()
start_new_thread(MainProcess, (frame.Refresh,))
Edit:
The above code piece calling UI methods directly is wrong!
Worker thread should not directly manipulate GUI, instead it should inform the GUI thread about a change, and the GUI thread which is the main thread will handle it in its context. There are again several approaches here, the quickest to implement is through wx.CallAfter.
Which you can incorporate as below, instead of directly calling the function:
wx.CallAfter(refresh_callback)
Another way to handle the communication between worker thread and GUI thread is via wx.PostEvent.
class DrawPanel(wx.Frame):
"""Draw a line to a panel."""
def notify(self):
wx.PostEvent(self, wx.PaintEvent())
Then from the secondary thread, you can safely call frame.notify() whenever new data arrives.
For a more elegant solution involving wx.CallAfter, refer to https://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/ where pubsub is used.

Delay in method executing commands

I am having an issue with the portion of code below:
my_var = tk.StringVar(value="start")
my_label = tk.Label(self.root, textvariable=my_var)
def my_method(input):
my_var.set("working")
#make a method call to an external Python API; can take a few mins to finish
my_var.set("complete")
tk.Button(self.root, text="GO!", command=lambda: my_method(input))
I have a button that, when clicked, should execute my_method which should initially change the text in my_label to working, process some actions in an external API, then update the label to complete. What is actually happening is my_method is called, the label's text DOES NOT change, the API call is made which takes a few mins, then my label is updated to complete. I am assuming that it processes both the set commands quickly, but I only see the final set value.
Is there a reason why this is happening (have I done something wrong) and is there a solution to it? If this is expected behaviour, could someone explain why it is the case?
Whilst writing this, I thought about maybe launching a thread to execute the initial set call to see if that will work. I'd prefer a simpler solution though.
Tkinter needs you to return promptly from the callback, so that it can get back to the processing it needs to be doing in Tk.mainloop. Without being able to do this processing, it can't update your label's text. (Nor can it handle normal processing events like dragging the window, etc.)
The callbacks should never hang onto the main thread for long periods of time, as a result. If there's some time-consuming processing to do for them, it should be done in a separate thread.

Threaded Tkinter script crashes when creating the second Toplevel widget

I have a Python script which uses Tkinter for the GUI. My little script should create a Toplevel widget every X seconds. When I run my code, the first Toplevel widget is created successfully, but when it tries to create a second one the program crashes.
What I am doing is using the after method to call the function startCounting every 5 seconds alongside root's mainloop. Every time this function is called, I append a Toplevel widget object into a list and start a new thread which hopefully will be running the new mainloop.
I would be very grateful if someone could figure this problem out. By the way, this is just a little script that I am currently using to solve my problem, which is preventing me from going on with my real school project.
The code:
import threading,thread
from Tkinter import *
def startCounting():
global root
global topLevelList
global classInstance
topLevelList.append (Toplevel())
topLevelList[len(topLevelList)-1].title("Child")
classInstance.append(mainLoopThread(topLevelList[len(topLevelList)-1]))
root.after(5000,startCounting)
class mainLoopThread(threading.Thread):
def __init__(self,toplevelW):
self.toplevelW = toplevelW
threading.Thread.__init__(self)
self.start()
def run(self):
self.toplevelW.mainloop()
global classInstance
classInstance = []
global topLevelList
topLevelList = []
global root
root = Tk()
root.title("Main")
startCounting()
root.mainloop()
Tkinter is designed to run from the main thread, only. See the docs:
Just run all UI code in the main
thread, and let the writers write to a
Queue object; e.g.
...and a substantial example follows, showing secondary threads writing requests to a queue, and the main loop being exclusively responsible for all direct interactions with Tk.
Many objects and subsystems don't like receiving requests from multiple various threads, and in the case of GUI toolkit it's not rare to need specfically to use the main thread only.
The right Python architecture for this issue is always to devote a thread (the main one, if one must) to serving the finicky object or subsystem; every other thread requiring interaction with said subsystem or object must them obtain it by queueing requests to the dedicated thread (and possibly waiting on a "return queue" for results, if results are required as a consequence of some request). This is also a very sound Python architecture for general-purpose threading (and I expound on it at length in "Python in a Nutshell", but that's another subject;-).
Tkinter has issues dealing with input from multiple threads, I use mtTkinter instead, you won't need to change any code and everything will work fine. Just import mtTkinter instead of Tkinter.
You can get it here:
http://tkinter.unpythonic.net/wiki/mtTkinter
Is there a reason you want (or think you need) one event loop per toplevel window? A single event loop is able to handle dozens (if not hundreds or thousands) of toplevel windows. And, as has been pointed out in another answer, you can't run this event loop in a separate thread.
So, to fix your code you need to only use a single event loop, and have that run in the main thread.

Categories