Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

Responsive GUI without threads

by ruoso (Curate)
on Oct 25, 2006 at 09:44 UTC ( [id://580526]=perlquestion: print w/replies, xml ) Need Help??

ruoso has asked for the wisdom of the Perl Monks concerning the following question:

In Re^3: Parrot, threads & fears for the future., merlyn pointed that if "threads" is the answer, you asked the wrong question. For some tasks I do agree, some implementations does exceed the use of threads where forking could solve the same problem without the implicit problems of using threads. But one task still seems to me like one where "threads" is indeed the answer, which is making a responsive Graphical User Interface.

Considering the following code (currently without threads and, like that, unresponsive). How one could make that responsive without threads?

#!/usr/bin/perl use strict; use warnings; use Glib qw/TRUE FALSE/; use Gtk2 -init; # Code taken from Perl Gtk2 tutorial with some modifications... # http://gtk2-perl.sourceforge.net/doc/gtk2-perl-tut/sec-Statusbars.ht +ml my $window = Gtk2::Window->new('toplevel'); $window->set_size_request(200, 100); $window->set_title("gtk2-perl Statusbar Example"); $window->signal_connect(delete_event => sub { Gtk2->main_quit; FALSE; +}); my $vbox = Gtk2::VBox->new(FALSE, 1); $window->add($vbox); $vbox->show; my $status_bar = Gtk2::Statusbar->new; $vbox->pack_start($status_bar, TRUE, TRUE, 0); $status_bar->show; $status_bar->{count} = 1; my $context_id = $status_bar->get_context_id("Statusbar example"); my $button = Gtk2::Button->new("push item"); $button->signal_connect(clicked => sub { $status_bar->push($context_id, sprintf("Starting... Item %d", $status_bar-> +{count}++)); sleep 10; # some lenghty task $status_bar->pop($context_id); }); $vbox->pack_start($button, TRUE, TRUE, 2); $button->show; $window->show; Gtk2->main;
daniel

Replies are listed 'Best First'.
Re: Responsive GUI without threads
by jbert (Priest) on Oct 25, 2006 at 12:07 UTC
    To do this sort of thing in one process, as a general rule, you have to avoid operations which block or more generally, which take a long time.

    This means doing things like using async I/O, breaking long computations into chunks which can be called on a 'tick' basis etc.

    This can be a pain (as can servicing more than one event loop in a program). It is a different kind of pain than threading or fork+IPC.

    A 'normal' sequential program is essentially a list of "do A, do B, do C". It doesn't matter if doB takes a long time, you've got nothing else to do apart from doB anyway (since you don't have to doC until doB is done).

    The problem comes when your program wants to respond to things (user input, network connections etc). Because now it has (at least) two jobs to do concurrently - "listen for new events" and "handle event".

    You can then:

    1. Run a new sequential program to handle each task.

      This is both the thread model and the fork model. Both of these provide a new 'context of execution' which can then sit doing its own doA, doB, doC. Forking and threading only differ in what is shared between the two contexts (see "man clone" on a Linux box for a nice list of the sorts of things which might or might not be shared between two contexts of execution, classical processes and threads aren't the only options).

    2. Write in a different style. This is the event loop approach, where no operation is allowed to take a long time because you have to get back to service the event loop. (How long? Depends on how long you mind waiting for an event to be handled). You can break up a long operation by saving enough context for you to respond to the event you know it will generate in the future. (e.g. for an async read of a file into a buffer, your context will need to include the buffer and current position).

      Objects are great for encapsulating such context. As are closures.

      Your example shows another complication in event-based code, where you have more than one event system. In this case, you need one event loop to be the 'master' and a hook to the other to 'handle one event'. If you don't have that then you can't use this approach.

Re: Responsive GUI without threads
by zentara (Archbishop) on Oct 25, 2006 at 12:28 UTC
    This is a non-blocking-delay (shown to me by muppet), and it will solve your particular problem in the example.
    #sleep 10; &non_blocking_delay( 10000 ); sub non_blocking_delay { my $milliseconds = shift; my $mainloop = Glib::MainLoop->new; Glib::Timeout->add( $milliseconds, sub { print time, "\n"; $mainloop->quit; FALSE; } ); $mainloop->run; ; }
    However, you give a very simple and arbitrary example, and the above sub won't generally work. Depending on your needs, you might be able to use a piped-open to run your command( which basically forks it off ), then use a filehandle watch on it to collect results.
    Glib::IO->add_watch( fileno $fh, [ qw/in/ ], \&watch_callback, $fh ); sub watch_callback { my ( $fd, $condition, $fh ) = @_; my @lines = $fh->getlines; print @lines; #always return TRUE to continue the callback return TRUE; } }
    Also see Gtk2::Helper->add_watch, which is a bit easier. You can also use IPC::Open3 instead of a piped open. The only other thing you can do, is fork off the long process and communicate thru Shared Memory Segments.

    But after all that, it is often easiest just to use threads and shared variables. :-) Don't let the anti-thread hype get you down. If done properly, threads work great, and I'm sure threads will be in Perl6's future.


    I'm not really a human, but I play one on earth. Cogito ergo sum a bum
Re: Responsive GUI without threads
by Arunbear (Prior) on Oct 25, 2006 at 12:21 UTC
      And I would say also use POE and combine it with wxPerl so you'll get so good looking UI :)
Re: Responsive GUI without threads
by Fletch (Bishop) on Oct 25, 2006 at 11:40 UTC

    Most event loops have some form of do_one_event call that makes them tick through the next pending event then return. Find this routine for your particular GUI toolkit and intersperse calls to it throughout your long task.

      But what if the long task involves a single op code, like a sort, or a regex search with complex requirements on a large volume of data.

      Or if it involves a blocking call to an external source of data, like the filesystem, network, or a DB?

      Or a call to a cpan module that doesn't have any convenient callback mechanism--like any of the graphics processors, or many of the XML processors, or maths or statistics routines that need to process your large lump of data in one go?

      Or a call to the OS that takes as long as it takes and provides no mechanism for smaller granularity?


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Responsive GUI without threads
by jbert (Priest) on Oct 25, 2006 at 18:51 UTC
    Merlyn's pithy quote puts me in mind of one of Larry McVoy's, on the same subject: "Threads are like salt. You like salt, I like salt, but we eat a lot more pasta than salt."

    Which I take as saying that even if you thread, you shouldn't really have one thread per logical activity (as is common). Threads are at their best (as are processes) when their number is a small multiple (2 or 3) times the number of execution cores in your system (i.e. number of processors, give or take a bit of hyperthreading or dual-core action).

    This leads to approaches such as thread pools and producer/consumer relationships between threads. This also leads to healthy behaviour such as avoiding repeated startup/shutdown of threads and processes. (OSes vary as to the relative costs of starting up threads and processes. But even so, it isn't something you want in your fast path if you're looking at performance. And lots of people interested in threading are).

Re: Responsive GUI without threads
by dk (Chaplain) on Oct 26, 2006 at 13:21 UTC
    But one task still seems to me like one where "threads" is indeed the answer, which is making a responsive Graphical User Interface.

    Doubtful at least. Why GUI programs are different from any other programs? It is quite possible to display a progress of a long process by forking it off and reading its output, the only difference that the reading pipe should be non-blocking. I don't know what class you should use for Gtk, but the idea is roughly like this:

    use IO::Handle; use Fcntl; use POSIX qw(exit); $SIG{PIPE} = 'IGNORE'; my $handle = IO::Handle-> new; $handle-> autoflush(1); my $pid = open( $handle, '-|'); if ( $pid) { # parent # make it non-blocking my $fl = fcntl( $handle, F_GETFL, 0); die "$!" unless defined $fl; fcntl( $handle, F_SETFL, $fl|O_NONBLOCK) or die "$!"; # attach to your gui system so your callbacks are # pinged whenever something appears on $handle MyFictitiousGUI::FileListener->attach( $handle, on_read => sub { message( <$handle>); }, on_close => sub { message("it's over!") } ); } else { # child $|++; while ( do_computation) { print "$percents done\n"; } POSIX::_exit(0); }
    Most probably POE can do this as well, but it must know about you GUI toolkit event loop structure. If you're doing it by yourself though, you should also eventually call waitpid($pid,WNOHANG) so the child process won't become a zombie.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://580526]
Approved by Joost
Front-paged by bart
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others drinking their drinks and smoking their pipes about the Monastery: (7)
As of 2024-03-29 01:11 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found