wxPython hanging at random points in sequential code - python

I have a simple app that presents you with a button, when the button is pressed, some long running computations are done(not in a separate thread, there is no need for a separate thread as these computations are the sole reason of the app). These computations take place in a for loop and at every iteration, the for look has a callback to the main frame that updates it so it shows the current iteration(same with a small gauge underneath that fills up).
def _delay(self):
for i in range(15000):
self.update_stats(f'{i}', i)
def on_button_click(self, event):
self.init_completion_gauge(15000)
self._delay()
self.set_state_comparison_done()
def init_completion_gauge(self, maxval):
self.gauge.SetRange(maxval)
self.Layout()
self.Update()
def update_stats(self, current_iter, count):
self.label_current_iter.SetLabelText(
'\n'.join((f'Iter:', current_iter)))
self.gauge.SetValue(count)
self.label_percent_complete.SetLabelText(
f'{(count + 1) * 100 // self.gauge.GetRange()}%')
self.Layout()
self.Update()
When this runs, everything goes smoothly for a few seconds before the app shows Not responding in the title bar and hangs until it is complete. I don't mean is just hands and shows no error. Since the computations are done in the main loop it is intended for the app to block the main loop until they are done, but I doubt it is normal to randomly show not responding without being interacted with. After the computations are done the app behaves normally but it will stop responding at completely random points.
Any advice as to why this happens and what should be done to fix this?

GUI updates are queued.
Let's suppose your code looks like:
myLongTask
{
loop:
... do something
UpdateGUI()
end loop
}
Each time you call UpdateGUI() a message is sent to the OS, and the control returns inmediately and continues with the loop.
When has the OS the oportunity to handle that message? It's too busy running that loop.
You could use wx.Yield or better set a thread for the long task, that sends messages to the OS requesting update.
See more here

Related

Which code does the mainloop() processes infinitely until any event occurs?

To understand my question kindly follow the paragraphs written below:
What code does the mainloop processes infinitely? Like does it read the code of the entire program again and again?
consider the code:
from tkinter import *
window = Tk()
print("lol")
print("Hello World")
window.mainloop()
the output didn't print "Hello World" or "lol" infinite number of times, so the mainloop() doesn't loop the code of the current module.
Now consider this code:
from tkinter import *
print("lol")
window = Tk()
print("Hello World")
while True:
window.update()
Now, even this code executes the same output, so now we can consider the mainloop() loops the code "window.update()" infite number of times, but more efficiently(somehow).
Now the first question arises what does the window.update() function do to update the values in the GUI, does it re-read the code from top to bottom again, or how does the update function update the GUI widget vaules.
The second question is :
I read this article
"Mainloop in Python Tkinter is an infinite loop of the application window which runs forever so that we can see the still screen.
The application window is like a frame that keeps on destroying every microsecond but the main loop keeps on creating a new updated window.
This process of destroying old window screens and creating a new one happens so fast that human eyes don’t even realize it.
Since the process runs infinite time that is why we are able to see the application in front of us and when we close the window then the loop terminates or exits."
Now if this is true then to recreate an updated window the root.mainloop() must read the entire root GUI code again and again entirely or is there another explanation to it.
I have been trying to understand this for the past 6hrs and I have visited every site and I cannot find the solution for the life of me.
Regards,
Rashik
What code does the mainloop processes infinitely? Like does it read the code of the entire program again and again?
No.
Via this function, it calls this C code which has the embedded Tcl interpreter process one event, or wait for Tkinter_busywaitinterval before trying to process another event
Now, even this code executes the same output, so now we can consider the mainloop() loops the code "window.update()" infite number of times, but more efficiently(somehow).
window.update() calls TCL update, which is described to
[...] bring the application “up to date” by entering the event loop repeatedly until all pending events (including idle callbacks) have been processed.
Your infinite loop doesn't have a sleep, so it's spinning your CPU as hard as possible to do practically nothing.
[...] Does it re-read the code from top to bottom again, or how does the update function update the GUI widget vaules.
It certainly doesn't re-read your code. It processes any pending widget updates, which may have happened by running e.g. window.text("...") in e.g. a click callback or an .after() timeout, etc.
I read this article [...]
That article seems wrong and/or at least over-simplifies things.
This simple example clock should clarify how things work:
import time
import tkinter as tk
root = tk.Tk()
text = tk.Label(root)
text.pack()
def tick():
text["text"] = time.ctime() # update `text` widget's content
root.after(1000, tick) # schedule for this function to be called after 1 second
if __name__ == '__main__':
tick() # call the `tick` function once before entering main loop
root.mainloop()

How to start and stop a while-loop with an ipywidget interactive Checkbox?

i try to start and stop a while-loop with a simple checkbox. But i don't know how i am able to update the checkbox-state in the while loop.
Below you can see what i have, but when i uncheck the box, the while loop won't recognize it.
I searched through the interactive functions but have not found any function to get the updated state of the checkbox.
Hope you guys can help me out :)
import ipywidgets
import time
def f(x):
while x==True:
print("!")
time.sleep(1)
c=ipywidgets.interactive(f,x=False)
c
If you create the checkbox explicitly with:
checkbox = widgets.Checkbox(description='click me')
... you can get the current value with checkbox.value.
Annoyingly, that's not enough to just allow your checkbox to halt the execution of a while loop. Once your while loop has started, it occupies the Python interpreter. There is therefore no space for the checkbox changes to be interpreted until the while loop finishes.
Since this is fundamentally a concurrency problem (you want to react to changes in the checkbox while also running a computation), you need to use Python's concurrency primitives. You can, for instance, achieve this with coroutines:
import ipywidgets as widgets
import asyncio
class HaltableExecutor:
def __init__(self, checkbox):
self._checkbox = checkbox
async def my_code(self):
# This is your user code
while True:
if self._checkbox.value:
print('running') # put your code here
await asyncio.sleep(0.1) # use this to temporarily give up control of the event loop to allow scheduling checkbox changes
def start(self):
print('starting')
asyncio.ensure_future(self.my_code())
Then, create and display your checkbox:
c = widgets.Checkbox(description='click me')
c
... and start your executor:
exe = HaltableExecutor(c)
exe.start()
The while loop will now run. At every iteration, we pause to give up control of the event loop with asyncio.sleep. The amount we sleep by is irrelevant, the point is to give control back. This gives the main thread an opportunity to deal with outstanding messages like checkbox changes. At the next iteration of the while loop, self._checkbox.value is checked again and so on.

wxPython: frame class loads several commands AFTER it is called

I have a function that runs a few intensive commands, so I made a Spinner class, which is just a simple window that appears with a wx.Gauge widget that pulses during loading.
The problem is that, when called in Run, the window doesn't appear until several seconds after it was initialized - self.TriangulatePoints() actually finishes before the window appears. Indeed, if I don't comment out load.End() (which closes the window), the Spinner instance will appear and immediately disappear.
I assume this has something to do with threading, and the program continues to run while Spinner initiates. Is this the case? And if so, can you pause progression of Run() until the Spinner window appears?
It should also be noted that running time.sleep(n) after calling Spinner(...) does not change when in the program sequence it appears on screen.
def Run(self, event):
gis.points_packed = False
gis.triangulated = False
load = Spinner(self, style=wx.DEFAULT_FRAME_STYLE & (~wx.CLOSE_BOX) & (~wx.MAXIMIZE_BOX) ^ (wx.RESIZE_BORDER) & (~wx.MINIMIZE_BOX))
load.Update('Circle packing points...')
gis.boundary(infile=gis.loaded_boundary)
load.Pulse()
self.GetPoints(None, show=False)
load.Update("Triangulating nodes...")
self.TriangulatePoints(None, show=True)
load.End()
########################################################
class Spinner(wx.Frame):
def __init__(self, *args, **kwds):
super(Spinner, self).__init__(*args, **kwds)
self.SetSize((300,80))
self.SetTitle('Loading')
process = "Loading..."
self.font = wx.Font(pointSize = 12, family = wx.DEFAULT,
style = wx.NORMAL, weight = wx.BOLD,
faceName = 'Arial')
self.process_txt = wx.StaticText(self, -1, process)
self.process_txt.SetFont(self.font)
self.progress = wx.Gauge(self, id=-1, range=100, pos=(10,30), size=(280,15), name="Loading")
self.Update(process)
self.Centre()
self.Show(True)
def End(self):
self.Close(True)
def Update(self,txt):
dc = wx.ScreenDC()
dc.SetFont(self.font)
tsize = dc.GetTextExtent(txt)
self.process_txt.SetPosition((300/2-tsize[0]/2,10))
self.process_txt.SetLabel(txt)
self.progress.Pulse()
def Pulse(self):
self.progress.Pulse()
There doesn't seem to be any threads in the code you show, so it's really not clear why do you think this has anything to do with threading. Quite the contrary, in fact: AFAICS this is due to not using threads. You should run your long running ("intensive") code in a worker thread, then things would work and display correctly in the UI.
You can't block the main, UI thread for any non-trivial amount of time and still expect the UI to update correctly.
By adding wx.Yield() immediately after load.Update('...'), I was able to fix the problem.
I found the solution through a post that Robin Dunn (#RobinDunn), one of the original authors of wxPython, wrote in a Google group:
As Micah mentioned the various yield functions are essentially a
nested event loop that reads and dispatches pending events from the
event queue. When the queue is empty then the yield function returns.
The reason [wx.Yield()] fixes the problem you are seeing is that your long
running tasks are preventing control from returning to the main event
loop and so the paint event for your custom widget will just sit in
the queue until the long running task completes and control is
allowed to return to the main loop. Adding the yield allows those
events to be processed sooner, but you may still have problems when
the long running task does finally run because any new events that
need to be processed during that time (for example, the user clicks a
Cancel button) will still have to wait until the LRT is finished.
Another issue to watch out for when using a yield function is that it
could lead to an unexpected recursion. For example you have a LRT
that periodically calls yield so events can be processed, but one of
the events that happens is one whose event handler starts the LRT
again.
So usually it is better to use some other way to prevent blocking of
events while running a the LRT, such as breaking it up into chunks
that are run from EVT_IDLE handlers, or using a thread.

Infinite loops in Python threads

So I have two python threads running from inside a class. I have checked using
threading.active_count()
and it says both threads are running. The first thread includes a tkinter window which works fine. The second thread I am using as an event manager for the first window, which also works okay by itself. However, when I run the second thread alongside the first thread, the first thread does not work, ie. the window does not appear. This is even if the first thread is executed first. When I remove the infinite loop from the second thread, the first thread works again, can anyone explain this to me? Here is the class:
class Quiz(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def show(self, question):
self.question = quiz[question][0]
self.correct = quiz[question][1]
self.incorrectA = quiz[question][2]
self.incorrectB = quiz[question][3]
self.ref = quiz[question][4]
questionDisplay.config(text=self.question)
correctButton = "answer" + str(self.ref[0])
eval(correctButton).config(text=self.correct, command=lambda : check(True))
incorrect1 = "answer" + str(self.ref[1])
eval(incorrect1).config(text=self.incorrectA, command= lambda : check(False))
incorrect2 = "answer" + str(self.ref[2])
eval(incorrect2).config(text=self.incorrectB, command= lambda : check(False))
return self.correct
def run(self):
print("thread started")
print(threading.active_count())
while True:
print(questionQueue.qsize())
if questionQueue.qsize() >= 1:
pass
else:
pass
print("looped")
Thanks
From the code as currently shown it is not obvious where the problem lies. But keep the following in mind;
Tk is event-driven like basically all GUI toolkits. So for the GUI to work you need to run Tk's mainloop. The only pieces of your code that it runs in the main loop are the various callbacks attached to things like buttons, menus and timers.
Like most GUI toolkits Tk isn't thread-safe because of the overhead that would require. To keep it working properly, you should only call Tk functions and methods from one thread.
Python's threads are operating system threads. This means they are subject to operating system scheduling. And the OS sometimes gives more time to threads that are busy. So if a thread that is spinning in a busy-loop is pre-empted (as is done regularly), chances are that it ends up being run again instead of the GUI thread.

Killing a multiprocess pool using a tkinter button

I'm building an application that does thousands (possibly millions) of calculations based off of what the user inputs. Because these calculations can take a long time, I want to use Python's multiprocessing module. Here is my dilemma; I want to set up a tkinter window with a cancel button to stop the calculations running throughout the processors I set up using Pool. I tried using threads to allow the popup window to run, but some funny things happen when I run the code.
When I press the 'start' button, the Pool starts going as expected. However, my popup window does not show even though it is on its own thread. Even weirder, when I tell my IDE (PyCharm) to stop the program, the popup window shows and the calculations are still running until I either press the 'exit' button from the popup window, or kill the program altogether.
So my questions are: Why won't my popup window show even though it is on its own thread? Am I utilizing the multiprocessing module correctly? Or is the problem something totally different?
import tkinter as tk
from tkinter import ttk
import random
import threading
import time
from multiprocessing import Pool
def f(x):
print(x*x)
time.sleep(random.randrange(1,5)) # simulates long calculations
# Extra math stuff here...
def processor():
global calc
calc = Pool(4)
calc.map(f, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30])
print("Done")
def calculation_start():
p = threading.Thread(target=progress_window) # Thread to open a new window for the user to cancel a calculation
p.start()
processor() # Calls the function to run the calculations
def progress_window():
# This is the window that pops up to let a user cancel a calculation.
popup = tk.Toplevel()
endButton = ttk.Button(popup, text="End", command=lambda :(calc.terminate()))
endButton.pack()
master = tk.Tk()
startButton = ttk.Button(master, text="Start", command=calculation_start)
startButton.pack()
tk.mainloop()
EDIT:
I tried switching the processor function to a thread instead of the progress_window function.
def calculation_start():
p = threading.Thread(target=processor) # Thread for the function to run the calculations
p.start()
progress_window() # Open a new window for the user to cancel a calculation
My popup window appears and the 'end' button looks like it stops the processor when pressed, but it never continues past that point. It's like it's stuck at calc.map(f, [1,2,3,...] in the processor() function.
From the Pool.map documentation:
It blocks until the result is ready.
That means that, yes, the work is being done on other threads, but the main thread (the one calling map) will not execute anything else until the other threads are complete. You want to use map_async and (for a long-running, interactive process), provide a callback for what to do when the pool is finished (probably hide the pop-up).
If you want your program to simply show the pop-up then exit, you want to use the AsyncResult returned by map_async. Set up an idle handler using after_idle (See tkinter universal widget methods and remember you'll need to manually re-add the handler after every call). After each idle call, you could call either AsyncResult.ready() or AsyncResult.get(<timeout>) to see if the pool has completed. Once it's finished, you're safe to process the results and exit.

Categories