Search code examples
perlperltk

Perl (tk): how to run asynchronously a system command, being able to react to it's output?


I'm writing a wrapper to an external command ("sox", if this can help) with Perl "Tk". I need to run it asynchronously, of course, to avoid blocking tk's MainLoop(). But, I need to read it's output to notify user about command's progress.

I am testing a solution like this one, using IPC::Open3:

{
    $| = 1;
    $pid = open3(gensym, ">&STDERR", \*FH, $cmd) or error("Errore running command \"$cmd\"");
}
while (defined($ch = FH->getc)) {
    notifyUser($ch) if ($ch =~ /$re/);
}
waitpid $pid, 0;
$retval = $? >> 8;
POSIX::close($_) for 3 .. 1024; # close all open handles (arbitrary upper bound)

But of course the while loop blocks MainLoop until $cmd does terminate.

Is there some way to read output handle asynchronously? Or should I go with standard fork stuff? The solution should work under win32, too.


Solution

  • For non-blocking read of a filehandle, take a look at Tk::fileevent.

    Here's an example script how one can use a pipe, a forked process, and fileevent together:

    use strict;
    use IO::Pipe;
    use Tk;
    
    my $pipe = IO::Pipe->new;
    if (!fork) { # Child XXX check for failed forks missing
        $pipe->writer;
        $pipe->autoflush(1);
        for (1..10) {
            print $pipe "something $_\n";
            select undef, undef, undef, 0.2;
        }
        exit;
    }
    $pipe->reader;
    
    my $mw = tkinit;
    my $text;
    $mw->Label(-textvariable => \$text)->pack;
    $mw->Button(-text => "Button", -command => sub { warn "Still working!" })->pack;
    $mw->fileevent($pipe, 'readable', sub {
                       if ($pipe->eof) {
                           warn "EOF reached, closing pipe...";
                           $mw->fileevent($pipe, 'readable', '');
                           return;
                       }
                       warn "pipe is readable...\n";
                       chomp(my $line = <$pipe>);
                       $text = $line;
                   });
    MainLoop;
    

    Forking may or may not work under Windows. Also one needs to be cautious when forking within Tk; you must make sure that only one of the two processes is doing X11/GUI stuff, otherwise bad things will happen (X11 errors, crashes...). A good approach is to fork before creating the Tk MainWindow.