Something similar to gobject.idle_add() in tkinter

Hello there,

I have come to adjust an old (but by me daily used) program using pygtk to be running on modern systems. I decided to switch to tkinter instead of GTK, because I want to reduce dependencies.

When rewriting some code I now ran into a problem for which I cannot find a solution by searching the web or the forum:

I need a function being executed in the mainloop after another function in a background thread is finished. The problem is also that this background thread is running a for loop and has to run the mentioned function to be executed in mainloop many times; after every for loop run.
The function must be executed in the mainloop to update GUI widgets and so it has to be done thread-safe.
That’s why I was using

gobject.Idle_add()

for this in the original program.
Now I am searching for a replacement for this in tkinter.

Any ideas?

Background: The program is loading application icons of all installed applications of the system in background so that the application list can be shown immediatly with empty-icon-placeholders and is filled with the correct icons step by step withour interrupting the user’s actions…

Hi,

are you referring somethin akin to interrupts?

Hello,

thanks for your reply.

I think it’s more like the opposite I want.
I want to give the main thread another task to be performed after the mainloop’s current run is done completely.

The problem is: As soon as I overwrite the placeholder image object with a new image (new file converted to the same image object) I get a crash, because the same memory slot is being used by both threads I guess.

What gobject.idle_add() did: It schedules another task (defined by a function) to be executed at the end of the mainloop just as it had been triggered by the user using a button (as far as i know).

Why not run that for loop in a sub-while loop until it is complete?
You want to have this task running until it is 100% complete. Afterwards, you want a function to perform

task1_flag = True
task2_flag = False
condition_is_met_flag = False

while True:  # main loop

    while task1_flag is True:
        # a for loop and has to run the mentioned function to be executed in mainloop many times
        for x in items:
            
            # iterations as per x in items
        
        # after every for loop run
        func()  # function to run after the mentioned for loop

        # Perform tasks as required per task 1
        # Update condition_is_met_flag

        if condition_is_met_flag == True:

            task1_flag = False  # Update flag because task is completed
            task2_flag = True
            break

    if task2_flag is True:  # Not a while loop since you stated as a simple task

        # perform tasks for task2

Something like this?

If not, there is another way if you don’t want to be stuck in a sub-while loop. You can use if-else statements and use user defined flags similar to the the previous approach. So long as the flag is True, it will keep executing that task in the main loop as it keeps looping until the condition has been met to clear it. Once the flag has been cleared (False), then the 2nd task function will be executed.

Maybe the after_idle(func,*args) widget method? See:
https://tkdocs.com/shipman/universal.html

I’d expect you could call this from another Thread.

1 Like

Yes, this sounds like exactly what after_idle is for.

I recommend against explicitly introducing your own threading for Tkinter if it isn’t absolutely necessary. Just schedule callbacks and handle events, and let Tkinter’s mainloop handle the rest.

Lucas seems well aware of this objective and it seems to motivate his
question. He’s got a worker running in the background which prepares
icons from the sound of it, and needs to schedule a tkinter callback to
update things as needed.

Hi together,

thanks for your input.
I have seen the after_idle() function in the last days when doing my own search for a solution.
I read about it and came to the conclusion that it would not do what I want.
But the description mentioned above seems to change my mind.
I guess it could do the right thing, but I have to do it all the other way round so that my background thread populates a list of available image objects, that have been updated and the function which will be called by mainloop’s after_idle() function will read that list and update the corresponding widgets.

I guess that list should perhaps be a queue as it is suggested as thread safe “communication” channel in tkinter multithread programs.

I guess I have to build a little proof of concept program to verify, because implementing that within the existing program of mine will be a little more complex and needs a little effort.
I’m also not sure if I should just rewrite the whole program from scratch now - even if this will take longer and I really like to get it running again on modern systems soon…

One question concerning the after_idle() methid still remains: It has to be used on a tkinter widget object as I understand. But it seems, it does not really matter which one? So I could just use it on the main window widget and it will do the job?
Or is the “idle state” only checked for widgets below or even above in hierarchy of widgets in some way? But in my opinion it is all one mainloop and so it should not make a difference…

Thanks,

Lucas

I have seen the after_idle() function in the last days when doing my
own search for a solution.
I read about it and came to the conclusion that it would not do what I want.
But the description mentioned above seems to change my mind.

Could you explain why you thought that? It is aimed at doing things
after stuff like widget layout has settled, but it’s also a mechanism
for getting a callback inserted into the mainloop, to run once.

I guess it could do the right thing, but I have to do it all the other
way round so that my background thread populates a list of available
image objects, that have been updated and the function which will be
called by mainloop’s after_idle() function will read that list and
update the corresponding widgets.

Yes. To avoid contention I’d have a small object to contain the list,
with a lock. The worker makes an image object, takes the lock and
appends it to the list. The after_idle callback takes the lock,
switches out the list for an empty one, releases the lock, and processes
the list it collected.

I guess that list should perhaps be a queue as it is suggested as thread safe “communication” channel in tkinter multithread programs.

That can work. The worker would put image objects onto the queue and the
after_idle callback function would run a quick loop while not q.empty()
to collect images. Not overt locks required because the queue contains
them.

One question concerning the after_idle() methid still remains: It has
to be used on a tkinter widget object as I understand. But it seems, it
does not really matter which one? So I could just use it on the main
window widget and it will do the job?

Yes, or whatever widget is to hand when you’re doing things. The widget
is just the gateway into the tkinter system to call after_idle().

Or is the “idle state” only checked for widgets below or even above in hierarchy of widgets in some way? But in my opinion it is all one mainloop and so it should not make a difference…

It doesn’t. The mainloop processes events and updates the GUI. At the
end of each batch of updates it will also run the after_dile
callbacks. Once - it will need resubmitting. Which the callback can do
itself.

It does occur to me that if the GUI’s idle the callback won’t run until
something happens.

Hmm. You could avoid the queue and the alternative pick-up-the-list
approaches entirely. Have the worker submit a callback to process a new
image object for every image. The callbacks submitted with
after_idle() are effectively a queue, and I would expect them to wake
up the mainloop if it were already idle. This would need to be tested.
But with this approach the worker could go:

 ... up the top with the imports ...
 from functools import partial

 ... in the worker ...
 ... prepare a new image object ...
 main_widget.after_idle(partial(process_new_image, new_image_object))
1 Like

Hi again and thanks again for that detailed answer (and thinking).

It appears to me that I didn’t expect that the after_idle() method would be effective no matter what widget it is attached to. So perhaps that is why I did think it would be no solution for my task.
But I think I now understand that the widget is in fact just the gateway to the tkinter system.

I guess you are right what you wrote last; after all I read it seems that after_idle() does exactly what I need just the way gobject.idle_add() did before in my pygtk version of the application.

I will go for that way and I am curious if it just works then.
Effective testing will still take a while as I have to track down all the needed changes in my code for switching to tkinter…

Thanks a lot - all od you - for your efforts!

Lucas

2 Likes