Perl question - thread/fork?
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
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:
my $spin = Term::Twiddle->new();
print "Starting analysis. Please wait... ";
Term::Twiddle's on CPAN, and that should do everything you asked for.
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)
Readonly my $TEN_MB => 1024 * 1024 * 10;
my $progress = Term::ProgressBar->new($TEN_MB);
my $process = Proc::Background->new("programThatTakesAges");
# 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'));
# or: $progress->update((stat('/path/to/output')));
# 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
# 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.
# 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;
More information about the MiltonKeynes-pm