Perl question - thread/fork?

Nik Clayton nik at ngo.org.uk
Mon Sep 11 04:59:26 PDT 2006


Lee Larcombe wrote:
> I am trying to work out how I could call a program from within a perl
> script, and display some sort of indication to the user that it is running
> which stops when it is finished.
> 
> So I have eg:
> 
> Print "Starting analysis. Please wait...\n";
> 
> System "programThatTakesAges";
> 
> The 'programThatTakesAges' analyses some files and can take 10,20,30 mins or
> so (could be longer), then the perl script carries on and does things with
> those files. I would like the user to have some feedback that something is
> happening after the 'Please wait...' - a spinny cursor would be nice :-)
> 
> Anyway, I can't work out how to get the script to do the system call and
> then do something whilst it waits for it to finish. I've been looking at
> using a thread or a fork - I'm sure it must be one of those - but I can't
> really work it out.
> 
> Anyone got any tips or example code that would help?

You have two problems here.  The first is how to start a new process in 
the background, and wait for it to finish.  The second is how to draw a 
spinner.

You can handily solve both these problems with Term::Twiddle, which 
takes the opposite approach -- namely, it handles updating the spinner 
in the background for you, and assumes that the long process will happen 
in the foreground.  So your code looks like this:

   use Term::Twiddle;

   my $spin = Term::Twiddle->new();

   print "Starting analysis.  Please wait... ";
   $spin->start();
   system 'programThatTakesAges';
   $spin->stop();

Term::Twiddle's on CPAN, and that should do everything you asked for.

Extra credit:

If you know how long the process is going to take, or how far through 
the process the work is (or you can determine that, e.g., by examining 
the size of a file that's being created) then your users will probably 
prefer a progress bar.

Term::ProgressBar or Term::Activity can handle the nitty gritty of 
actually drawing the progress bar for you.

To start your long running process in the background you can either do 
the fork/exec yourself, or use a module.  If you're not comfortable with 
the semantics behind fork/exec then Proc::Background or Proc::Fork 
(again, on CPAN) are probably your best bet, since the abstract away 
most of the tricky stuff.

For example, if you know that your long running process is always going 
to create a 10MB output file (and your program can't do anything until 
the output file is complete) then something like this might work (note: 
untested code, may have typos)

   use strict;
   use warnings;

   use Readonly;
   use Proc::Background;
   use Term::ProgressBar;

   Readonly my $TEN_MB => 1024 * 1024 * 10;

   my $progress = Term::ProgressBar->new($TEN_MB);
   my $process  = Proc::Background->new("programThatTakesAges");

   do {
       # Get the file size, update the progress bar, which will
       # scale the result to fit in the bar

       my $file_size = (stat('/path/to/output'))[7];
       $progress->update($file_size);

       # or: $progress->update((stat('/path/to/output'))[7]);
       # but that's less readable, and only saves you a variable

       # Sleep, to avoid issuing millions of stat() calls.  Also means
       # that the progress bar is updated once per second
       sleep(1);
   } while($process->alive());

   # There's a chance that the progress bar is not at 100% here.  For
   # example, suppose we stat'd the file, and it was 9.8MB in size,
   # so the progress bar was updated to 98%.  Then we slept for a
   # second, and in the intervening time the process finished, so the
   # $process->alive() call returned false.  So processing is complete,
   # but the bar still says 98%.
   #
   # So, explicitly set the progress bar to 100% (i.e., the value we
   # passed to the constructor) so that the display looks clean.  You
   # don't have to do this, but it avoids confusing users.

   $progress->update($TEN_MB);

   # Print some information about the process

   my $ec = $process->wait(); # To collect the exit code / avoid zombies
   print "Process complete:\n";
   print "  pid was:        ", $process->pid(), "\n";
   print "  exit code was:  ", $ec / 256, "\n";
   print "  elapsed (secs): ", $process->end_time - $process->start_time;

N


More information about the MiltonKeynes-pm mailing list