Delay in method executing commands - python

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.

Related

Tkinter, threads, several widgets to update periodically

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()

python tkinter window going unresponsive and hanging

MyButton1 =Button(master, text='Quit',bg="grey",width=20,
command=master.quit)
MyButton1.place(x=200, y=100)
MyButton2 =Button(master, text='Propagate', bg="grey",width=20,
command=mainmethod)
MyButton2.place(x=1000, y=100)
master.geometry("1500x1500")
master.mainloop( )
In the above code after pressing propagate button mainmethod is invoking..
I wrote my logic in main method where this method alone taking 2minutes to execute in the mean time GUI going unresponsive state for few min and later displaying all my required output on text box i inserted
whether any away to avoid the unresponsive issue apart from using multi threading
and i am looking such that after pressing propagate button button should disabled and window should not go unresponsive and display text.insert statements continuously which i added in main method ?????
To prevent hanging, you need to separate the calculations in the mainmethod from Tkinter's main loop by executing them in different threads. However, threading system in Python is not that well-developed as in other languages (AFAIK) because of GIL (Global Interpreter Lock), but there is an alternative - using processes just like threads. This is possible with multiprocessing library.
In order to just prevent hanging, you could create another function
from multiprocessing import Process
def mainmethodLaunch():
global mainmethodProcess
mainmethodProcess = Process(target=mainmethod)
mainmethodProcess.start()
And bind this function to MyButton2 instead of mainmethod itself.
Docs: https://docs.python.org/2/library/multiprocessing.html#the-process-class
You can see p.join in the example. join method will cause your main process to wait for the other one to complete, which you don't want.
So when you press the button, mainmethodLaunch function will be invoked, and it will create another process executing mainmethod. mainmethodLaunch function's own run duration should be insignificant. Due to usage of another process, Tkinter window will not hang. However, if you do just this, you will not be able to interact with mainmethod process in any kind while it will be working.
In order to let these processes communicate with each other, you could use pipes (https://docs.python.org/2/library/multiprocessing.html#exchanging-objects-between-processes)
I guess the example is quite clear.
In order to receive some data from the mainmethod process over time, you will have to poll the parent_conn once a little time, let's say, second. This can be achieved with Tkinter's after method
(tkinter: how to use after method)
IMPORTANT NOTE: when using multiprocessing, you MUST initialize the program in if __name__ == '__main__': block. I mean, there should be no doing-something code outside functions and this block, no doing-something code with zero indent.
This is because multiprocessing is going to fork the same Python executable file, and it will have to distinguish the main process from the forked one, and not do initializing stuff in the forked one.
Check twice if you have done that because if you make such a mistake, it can cost you hanging of not just Tkinter window, but the whole system :)
Because the process will be going to fork itself endlessly, consuming all RAM you have, regardless of how much you have.

Tkinter issue with multiple callback functions and use of after()

I am working on a very long piece of code and one which I would like to take GUI input more than once in multiple windows.
The code is too long and wouldn't make much sense if I put it in here, but here's the layout of my code:
callback1():
do something
tkinter stuff, using callback1()
...
lots of other code (none to do with tkinter)
...
callback2():
do something else
tkinter stuff using callback2()
some more code (none to do with tkinter)
mainloop()
Sorry for the vague structure, but this is more or less the layout of my code. The problem is though, that mainloop() never seems to be reached, and my other code in between the callbacks relies on the input from the GUI and so it crashes.
I've tried:
callback1():
do something
after(500, callback1)
tkinter stuff, using callback1()
...
lots of other code (none to do with tkinter)
...
callback2():
do something else
after(500, callback1)
tkinter stuff using callback2()
some more code (none to do with tkinter)
after(0, callback1)
after(0, callback2)
mainloop()
But had no luck. Can anyone help me with the correct usage of after() (or any other function) so that I can just get the GUI to display?
GUI frameworks like tkinter are event-driven. They start running when they start their mainloop(). Everything before that is basically set-up and housekeeping.
After the mainloop starts, the only pieces of your code that they run are callbacks that you've attached to interface elements like buttons or to timers with after.
These callbacks must be relatively short and should not take a long time. The GUI toolkit calls the function you provided to handle an event in the mainloop. So while your callback is running, no further events are being processed and the GUI is effectively frozen. If that only takes a little while (say 50 milliseconds) nobody will notice.
But if you want to run a computation that takes several seconds to complete, you cannot do that in a callback without freezing the GUI.
You could consider doing the long-running job in a second thread. But in CPython only one thread at a time can be executing Python bytecode for technical reasons. However (especially in Python 3) threads are forced to relinquish the CPU regularly, so this might be a workable solution.
Another solution is to farm long-running jobs out to a separate process using multiprocessing. In this case you do have to set-up communication between the processes via e.g. a Queue.
You can't organize your code like that. Presumably, you have something similar to this:
import tkinter as tk
mainwindow = tk.Tk()
mainwindow.geometry("300x200")
mainwindow.title("MyWindow")
label = tk.Label(mainwindow, text="Enter stuff, then click the button.")
label.pack()
var = tk.StringVar()
entry = tk.Entry(mainwindow, textvariable=var)
entry.pack()
#callback1():
def onclick():
#do something with var.get() ?
pass
button = tk.Button(mainwindow, text="click me", command=onclick)
button.pack()
#lots of other code (none to do with tkinter):
print("The value I need is: {}".format(var.get()))
mainwindow.mainloop()
The output of that program is:
The value I need is:
You need to put your code in either the callback function, or another function that is called by the callback function:
def my_func():
print("The value I need: {}".format(var.get()))
def onclick():
#do something with var.get()
my_func()
button = tk.Button(mainwindow, text="click me", command=onclick)
Now, the output is something like:
The value I need is: 10
tkinter is a GUI framework, and it responds to events that happen in your window. The way you respond to an event is to wrap your code in a callback function and pass the function to tkinter, which will then call the function at the proper time. You do not write arbitrary code in the middle of a tkinter program.
The after() method does not seem appropriate, because it essentially says:
After the mainloop starts, execute this function.
You can delay the execution for however many seconds you want--but that doesn't mean that the user will have entered the data you need by then. What you want to do is execute your code after the user enters their data, and usually you do that by responding to a button click, where the button says, Submit, or some other event.

PyGObject (Glade) Window Never Showing (Multithreaded)

I've been fighting for three hours now to get this process multithreaded, so that I can display a progress box. I finally got it working, insomuch as the process completes as expected, and all the functions call, including the ones to update the progress indicator on the window.
However, the window never actually displays. This is a PyGObject interface designed in Glade. I am not having fun.
def runCompile(obj):
compileWindow = builder.get_object("compilingWindow")
compileWindow.show_all()
pool = ThreadPool(processes=1)
async_result = pool.apply_async(compileStrings, ())
output = async_result.get()
#output = compileStrings() #THIS IS OLD
compileWindow.hide()
return output
As I mentioned, everything works well, except for the fact that the window doesn't appear. Even if I eliminate the compileWindow.hide() command, the window never shows until the process is done. In fact, the whole stupid program freezes until the process is done.
I'm at the end of my rope. Help?
(By the way, the "recommended" processes of using generators doesn't work, as I HAVE to have a return from the "long process".)
I'm not a pyGobject expert and i don't really understand your code. I think that you should post more. Why are you calling the builder in a function? you can call it at the init of the GUI?
Anyways.. It seems that you are having the common multithread problems..
are you using at the startup GObject.threads_init() and Gdk.threads_init() ?
Then, if you want to show a window from a thread you need to use Gdk.threads_enter() and Gdk.threads_leave().
here is an useful doc
I changed the overall flow of my project, so that may affect it. However, it is imperative that Gtk be given a chance to go through its own main loop, by way of...
if Gtk.events_pending():
Gtk.main_iteration()
In this instance, I only want to call it once, to ensure the program doesn't hang.
(The entire program source code can be found on SourceForge. The function in question is on line 372 as of this posting, in function compileModel().

Tkinter window says (not responding) but code is running

I have a program that runs a long process after you click an action button. As the process is running the root window will say that it is not responding even though I know the program is running in the background. This program is going to be released to a few people that I work with and I want to make sure they don't freak out and close the window when they see this. The solution I have is sitting a root.update in the loop of the process that is running but I am not sure this was the best fix.
Using the python 3.3
Here is a sample of the code so you get an idea of what I am doing, this is called from the main loop:
def combine(boxes_to, boxes_from, frame):
to_value,to_pos = gui.checkBoxes(boxes_to)
from_value,from_pos = gui.checkBoxes(boxes_from)
frame.destroy()
running = Label(root,text="Running please do not close..",font = (16))
running.pack()
root.update()
map_to = open("map_to",'r')
for line in map_to:
root.update()
process(line)
running.destroy()
map_to.close()
finish = Button(root, text="Done",command=gui.stop)
finish.pack()
While you can call root.update() in your loop, this will still produce some (potentially) undesirable side-effects.
The program may act laggy, meaning it takes a long time to respond to user input.
You will only be able to run this one action. Any other action has to wait for this to finish.
As an alternative I would suggest that you implement simple multi-threading. Python multithreading is pretty simple, and will prevent both of these drawbacks. You will be able to execute your long running code, while still providing a clean and responsive UI.
If your application is trivially parallelizable, you could use multiple threads to decrease running time. Ex. Thread 1 handles entries 1-100, while thread 2 handles entries 101-200.
The best you can do here is to use multithreading in Python. Here's how to do this:
Let's say you have a function named combine() due to which the window is freezing, which is being used as a command for a button named 'btn' as shown here:
btn = Button(root, text="Click Me", command=combine)
Now, when btn is pressed you might be getting the 'not responding' problem. To fix this, edit the code as shown below:
import threading
btn = Button(root, text="Click Me", command=threading.Thread(target=combine).start)
Here threading.Thread creates a separate thread in which the combine() method is executed, so the GUI can continue to keep responding while the command is being executed.

Categories