I have a GUI that processes messages serially as they arrive and get queued up by several websockets. This function sets up a callback to itself using after_idle to perpetuate the process, like so:
def process_queue(self, flush=False):
'''
Check for new messages in the queue periodically.
Sets up callback to itself to perpetuate the process.
'''
if flush:
while not self.queue.empty():
self.get_msg()
else:
self.get_msg()
self.master.after_idle(self.process_queue)
I have an OptionMenu
widget on the GUI, which hangs and causes the program to crash when it is clicked:
self.plotind = tk.StringVar()
self.plotind.set('MACD')
options = ['MACD']
self.indopts = tk.OptionMenu(self.analysis_frame, self.plotind, *[option for option in options])
self.indopts.grid(row=1, column=1)
If I change the after_idle()
to just after()
, it works fine.
I assume this is because clicking on an OptionMenu
actually sets up its own after_idle()
call to open the menu, which is then competing with the one I have in process_queue()
.
I can certainly use after()
in my function if need be - it might not be optimally fast at processing the queue, but it's not the end of the world. But is there a more graceful way to handle this? Surely most GUIs should be able to handle having after_idle()
called somewhere when an OptionMenu
exists?
Generally speaking, you should not call after_idle
from a function called via after_idle
.
Here is why:
Once tkinter starts processing the idle queue, it won't stop until the queue is empty. If there is one item in the queue, tkinter pulls that item off and calls the function. If that function adds something to the queue, the queue is no longer empty so tkinter processes the new item. If this new item puts something on the idle queue, then tkinter will process that, and so on. The queue never becomes empty, and tkinter is never given the chance to do anything else besides service this queue.
A common solution is to use after
twice so that the queue has a chance to become empty, and thus allows tkinter to process other non-idle events.
For example, instead of this:
self.master.after_idle(self.process_queue)
... do this:
self.master.after_idle(self.master.after, 1, self.process_queue)
This creates a tiny window for the queue to become empty, allowing tkinter to process other "non-idle" events such as requests to redraw the screen before calling self.process_queue
again.