Re: Why doesn't my label show up in the window



Hi,

I haven't read the linked program, but based on the dummy program and
the previous discussion:

Le 26/08/2012 19:13, Frank Cox a écrit :
On Sun, 26 Aug 2012 17:36:01 +0700
Ardhan Madras wrote:

[...]

The objective of the program is to maintain an email mailing list for my
theatre.   I have a webpage here
http://www.melvilletheatre.com/mailinglist.html where interested people can
sign up to receive an email from me every time I have a new movie booked.  I
wrote a program a few years ago using ncurses to manage the mailing list and
send out the email and now that I want to learn to use GTK I decided to rewrite
that mailing list program as a learning project.

A GUI program should never block the GUI thread.  Blocking the GUI
thread has many kind of unwanted implications, all resulting of the GUI
unresponsiveness.  It's even worse than with a "CLI GUI" like ncurse,
because generally in "real" GUI, the GUI is responsible of drawing
itself and may need to do that at *any given time* -- e.g. after its
window have been brought back from behind another window.  If the GUI
doesn't refresh at this point, the user sees a blank window [1].

Anyway, what to remember is "never block GUI thread".  If you need to do
something that take a noticeable amount of time (e.g. > ~100ms), don't
do that in the GUI thread, or split it in steps taking less than 100ms.

The first solution is to split the long work into smaller steps and ask
the main loop to run these steps in idle time (see g_idle_add() and
friends).  This works if you can split your greedy work into small
pieces, but not all kind of work splits well like that, and it still
blocks for Xms (but in-between, all events are handled normally since we
use the main loop dispatcher).

The second solution is to use a separate thread to do the work.  This
may look simpler since you don't have to split up the algorithm, but
beware threading.  It's probably the right solution if you have a
complex algorithm or no control over what takes a long time to proceed,
but if you do anything wrong with threading, your program will blow up
faster than an A bomb.  Note that GTK *is NOT thread-safe!*  This
basically means you MUST NOT, EVER, call any GTK function from another
thread than the one which called gtk_main() (normally the application's
main thread).  To update the GUI from the worker thread you'll need to
"message" the GUI thread in some way.  Fortunately, there are some
facilities to do that.


For example, I attached a dummy program that replicates yours but using
threading.  I replaced the label with a dummy progress bar just to be a
little more realistic (although the progress bar isn't really
controlled), but you can very well put whatever widget you want here.  A
few details:

The main() is the same as yours.

The button_clicked_handler() is the callback called when the button is
clicked (like your subroutine()).  What it does it creating the second
window, then launching a second thread (worker()) to perform the
time-consuming operation.

update_progress_in_timeout() is registered as a timeout function (a
function called every Xms by the main loop) to update the progress bar
every 100ms.  This is totally dummy and don't reflect a progress (it
pulsates actually), but it shows clearly that the UI thread is responsive.

The worker() function is, as said above, launched in a separate thread.
 It first does the time-consuming operation (here a simple g_usleep()
like your sleep() to wait 5 seconds), and then registers an idle
function (a function called by the main loop whenever it has nothing
more important to do) which will update the UI
(worker_finish_in_idle()).  This is an useful trick: functions called by
the main loop (like idle or timeout functions) are, obviously, launched
in the main loop thread (the gtk_main() one then) -- and, as said above,
GTK calls MUST be done from the main loop thread.  Fortunately, the
GLib/GTK main loop is thread-safe, and so you can call
g_idle_add()/g_timeout_add() and friends from any thread, using those to
"message" the main thread (actually call a function on it).

Finally worker_finish_in_idle() stops the progress bar updates and
destroys the window.

You can also see I used a structure, WorkerData, to pass data to the
worker thread and the further.  This is needed because I wanted to pass
more than a pointer to my various functions and they only expect one
pointer argument, and so it allows me to pass as much data as I want.
If you only need one pointer, you don't need it, but you'll probably
actually need a lot more stuff than what I passed myself.  But beware
here too: don't access the data in that structure from different threads
at the same time, prefer deal with a copy when possible (or use locks,
though locks are complex and somewhat evil if they can be avoided).

Of course, if for any reason you access any data simultaneously from
worker() and an idle or timeout function, you must be aware of the
synchronizations issues related to threading like with any other
threaded program (as said above, locks or any other mean to prevent
concurrent read and writes).


Finally, if you can avoid using threads, do so.  If you could avoid it
and still don't listen to me, you either know what you do and don't need
my advices, or you're somewhat crazy :)

I hope this somewhat helps.

Regards,
Colomban


[1] This is less true with compositing window manager since those
generally keep "screenshots" of the windows and won't loose them if the
window doesn't redraws appropriately.  It's e.g. how Compiz can "gray
out" unresponsive windows.


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