Re: GUI freeze and long blocking operation



On Fri, Jun 14, 2013 at 8:28 AM, Kip Warner <kip thevertigo com> wrote:
On Thu, 2013-06-13 at 08:59 +0100, jcupitt gmail com wrote:
Hi Kip,

Hey John,

There are two easy ways to do a long operation in Python.

First, with idle_add(). Your callback should run for no more than 50ms
or so before returning. If you need to do more work than that, just
wait to be called again. Do not process events, you can leave that up
to the main loop. This style is handy for non-blocking, background
tasks that don't need interaction from the user.

Ok, fair enough. I didn't know idle callbacks were intended to be used
only for fast non-blocking tasks.

Secondly, with a regular loop that takes control for a long period.
You can keep the GUI alive by processing events, as you say above.
This style is better for modal actions that either block the GUI or
may require interaction.

So as I mentioned I tried abandoning the idle_add() approach and instead
relied on calling the worker function directly.

It sounds like you have done both at the same time, which seems
confusing to me. I'd make a pure 2) one. If the GUI doesn't refresh,
you probably have a bug in your code somewhere.

I do this by calling the long job function directly from within the
GtkAssistant's "prepare" signal callback immediately. The problem is
that the GUI doesn't refresh throughout the duration of the long job,
even though I do explicitly pump the message queue by calling
_updateGUI() method regularly:

Kip,
   Let me try to illustrate what you need to do to make this work.

What you currently have is:
~~~~~~~~~~~~~~~~~~~~~

prepare_signal_callback ()
{
    /* Update the GUI and flush the event queue, only once, at the
beginning of your operation */
    while (gtk_events_pending())
        gtk_main_iteration_do ();


    /* Process some data that takes a long time, without ever again
updating the GUI */
    while (there_is_data)
        process_data();
}

What you need to do instead is:
~~~~~~~~~~~~~~~~~~~~~~~

prepare_signal_callback ()
{
    /* Do your huge intensive loop that takes a long time */
    while (there_is_data)
    {
        /* Process a chunk of your long data operation */
        process_only_a_little_bit_of_data();

        /* Every once and a while, during your huge task, be friendly
to the user and show
         * some feedback, i.e. update the GUI often enough so that
things appear to run smoothly.
         */
        while (gtk_events_pending())
            gtk_main_iteration_do ();

    }
}

What you seem to be missing, is that GTK+ doesnt update itself in a
separate thread, your program
by default is single threaded and can only be processing data or
updating the GUI at a given time,
but not both.

If you have to call a function in a proprietary library which you
cannot modify, and that function
atomically takes a long time, the only way to keep the GUI responsive
during that time is to
add your own worker thread to call the function, and then signal the
main thread when the
operation completes (usually by queuing a g_idle_add() at the end of
your worker thread
so that the completion notification callback is queued to execute in
the main GUI thread).

Cheers,
    -Tristan


    # Update the GUI...
    def _updateGUI(self):

        # Update GUI...
        (...)

        # Flush the event queue so we don't block...
        while Gtk.events_pending():
            Gtk.main_iteration()

The GUI just appears to hang on the previously displayed page in the
assistant and doesn't advance to the one that actually should be visible
following the "prepare" signal callback.


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]