Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

Perl::TK - fileevent and script execution theory

by crabbdean (Pilgrim)
on Mar 12, 2004 at 06:30 UTC ( #336098=perlquestion: print w/ replies, xml ) Need Help??
crabbdean has asked for the wisdom of the Perl Monks concerning the following question:

Hi Fellow monks,

I just want to have small discussion on script execution in Perl::TK. If I have a script that takes a long time to execute I realise I have to call a "fileevent" on the file handle to read data without blocking and "freezing" the gui while the script executes.
$this -> fileevent(FILEHANDLE, "readable", \&callback);
Where I get stuck theory-wise is that what I'm executing would be a large directory read which in itself makes more subroutine calls while the filehandle is open. The callback is supposed to read and output what is read from the file handle. I'm just a little unclear how I'd write this into my MainLoop. The output is going to be put to a text widget or a log file.

Somethings not "clicking" for me. Am I making sense? Guidence appreciated.

Dean
The Funkster of Mirth
Programming these days takes more than a lone avenger with a compiler. - sam
RFC1149: A Standard for the Transmission of IP Datagrams on Avian Carriers

Comment on Perl::TK - fileevent and script execution theory
Download Code
Re: Perl::TK - fileevent and script execution theory
by zentara (Archbishop) on Mar 12, 2004 at 17:17 UTC
    It sounds like you are trying to do something complicated. This link may help explain the basics of events TPJ article on events

    While playing around, I've noticed some peculiarities with Tk::fileevent. If it's something with alot of output, you may need to throttle it a little bit. For instance, this first example will not produce output, but the second will. I need to study more on this. I believe it boils down to the filehandle needs to "be readable", it won't buffer for you.

    #!/usr/bin/perl use warnings; use strict; use Tk; startpiper(); my $mw = new MainWindow; my $t = $mw->Scrolled("Text",-width => 80, -height => 25, -wrap => 'no +ne'); $t->pack(-expand => 1); $mw->fileevent(\*CHILD, 'readable', [\&fill_text_widget,$t]); MainLoop; sub fill_text_widget { my($widget) = @_; $_ = <CHILD>; $widget->insert('end', $_); $widget->yview('end'); } sub startpiper{ open(CHILD, "ls -la |") or die "Can't open: $!"; }
    The following will run
    #!/usr/bin/perl use warnings; use strict; use Tk; open(CHILD, "./fileevent2piper 2>&1 |") or die "Can't open: $!"; my $mw = new MainWindow; my $t = $mw->Scrolled("Text",-width => 80, -height => 25, -wrap => 'no +ne'); $t->pack(-expand => 1); $mw->fileevent(\*CHILD, 'readable', [\&fill_text_widget,$t]); MainLoop; sub fill_text_widget { my($widget) = @_; $_ = <CHILD>; $widget->insert('end', $_); $widget->yview('end'); }
    And the piper script
    #!/usr/bin/perl $|++; for my $i ( 0 .. 10) { print $i, "\n"; print `ls -la`; sleep 1; }

    I'm not really a human, but I play one on earth. flash japh
      Okay, after doing some researching, reading and testing this is where I'm at with this ...

      Firstly, thanks zentara for the article, that was helpful. I also read through a tonne of other articles and code, even read through the Tk::ExecuteCommand module source code which was fantastic. Its all helped ratify my understanding and clarify certain aspects. The TK::ExecuteCommand module manages to achieve what I'm hoping to. So let me summise the problem and progress made so far....

      The first of your two examples above uses a call to startpiper(). I found his wasn't necessary and didn't make a difference, not sure if that was strategically intentional or just something written in by yourself. Surprisingly in my testing before writing this thread I'd written two very similar examples as the ones you did. Seems we went down similar paths. But your code helped clarify a few things also. So thanks mate.

      To help exaggerate the issue I face and show the type of problem I'm describing you'll see in the code below I've change the "ls -la" to "ls -laR c:". This attempts a much bigger call, freezing up the gui while it processes. Okay, so lets get into the juice of this theory and see if we can nut it out...

      The "fileevent" call binds itself to the "fill_text_widget" subroutine when the "CHILD" filehandle becomes "readable". Because this is a piped stream if you do a blocking (<>) call on it you'll freeze the gui. It requires reading the stream in bite size pieces and updating the text widget bit by bit. My code below demonstrates this based on code from the "TK::ExecuteCommand" and one I found in another manual. Interestingly, MOST INTERESTINGLY, my gui still freezes .... but something quite strange happens!! If I grab the window bar across the top of the window and move the window you can see it rapidly processing and appending the directory listing to the text box. Also I included a "bell" into the subroutine, and you can hear it looping through, and with the bell it doesn't freeze - at each bell the screen updates. WHY???!!!! Is the gui having troubles keeping up with the refresh? This is what I'm not sure of.

      So, any help is appreciated on this bit.

      Dean
      The Funkster of Mirth
      Programming these days takes more than a lone avenger with a compiler. - sam
      RFC1149: A Standard for the Transmission of IP Datagrams on Avian Carriers
      #!/usr/bin/perl use warnings; use strict; use Tk; open(CHILD, "ls -laR h: |") or die "Can't open: $!"; my $mw = new MainWindow; my $t = $mw->Scrolled("Text",-width => 80, -height => 25, -wrap => 'no +ne'); $t->pack(-expand => 1); CHILD->autoflush(1); $mw->fileevent('CHILD', 'readable', [\&fill_text_widget, $mw]); $mw->after(500); MainLoop; sub fill_text_widget { my ($widget) = @_; if (eof(CHILD)) { $widget->fileevent('CHILD', "readable", undef); # cancel bindi +ng return ; } if (sysread ('CHILD', $_, 4096)) { $t->insert('end', $_); # Append the data read $t->yview('end'); $mw->bell; } else { # sysread returned undef. Problem with file $t->insert('end', "ERROR !!!"); $widget->fileevent('CHILD', "readable", undef); # cancel bindi +ng } }
        I bet the continuously available fileevents are taking higher priority than the idleevents which is where the actual repaint is done. As Tk::Widget says:
        $widget->idletasks One of two methods which are used to bring the application +``up to date'' by entering the event loop repeated until all pendin +g events (including idle callbacks) have been processed. If the idletasks method is specified, then no new events or + errors are processed; only idle callbacks are invoked. This causes + opera- tions that are normally deferred, such as display updates a +nd win- dow layout calculations, to be performed immediately. The idletasks command is useful in scripts where changes ha +ve been made to the application's state and you want those changes +to appear on the display immediately, rather than waiting for +the script to complete. Most display updates are performed as i +dle callbacks, so idletasks will cause them to run. However, th +ere are some kinds of updates that only happen in response to event +s, such as those triggered by window size changes; these updates wi +ll not occur in idletasks.

        -- Randal L. Schwartz, Perl hacker
        Be sure to read my standard disclaimer if this is a reply.

        I'm experiencing your same problem with trying to figure out how fileevent works. I chose to use top for my experimenting. It's really tricky. I can get the output just fine with IPC3::Open, but fileevent won't read it. After googling on the topic, I see that fileevent hanging is a common problem, and it seems to boil down to how the "child" buffers it's output. And if you are'nt watching for it, Tk will hang until the child finishes outputting, which can be never for some utilities.

        I seem to have no problem with fileevent, if the child is a perl script and I set $|=1. But when calling external c apps, it's hit or miss, depending on the app. Sometimes app will not write output unless it has a tty to write to, and you need to use IO::Pty to fake a tty. Sometimes you have to do something to force it to give output. So it seems that there is no "easy rule" to follow which gives consistent results...each app you run as a child needs special consideration.

        Now I did find 1 example googling, which seems to be a "universal method" for getting fileevent to work, and it involves bypassing the pipe buffering problem by calling everything as a bash script, and reading bash's output. So maybe a "rule of thumb" is "start your child as a bash instance". Maybe "bash -c $cmd" would do it. Here it is:

        #!/usr/bin/perl -w use strict 'vars'; #by John Drukman $|=1; use Tk; use IPC::Open3; local *IN; local *OUT; local *ERR; my $pid=open3(\*IN,\*OUT,\*ERR,'/bin/sh'); my $inwin=new MainWindow; my $entry=$inwin->Entry(-width => 80)->pack; $inwin->Button(-text => 'Send', -command => \&send_to_shell)->pack; my $outwin=new MainWindow; my $textwin=$outwin->Text(-width => 80, -height => 24, -state => 'disabled')->pack; $outwin->fileevent(OUT,'readable',\&get_from); MainLoop; sub send_to_shell { my $cmd=$entry->get() . "\n"; write_textwin("> $cmd"); print IN $cmd; } sub get_from { my $buf=''; sysread OUT,$buf,4096; write_textwin($buf); } sub write_textwin { my $str=shift; $textwin->configure(-state=>'normal'); $textwin->insert('end',$str); $textwin->see('end'); $textwin->configure(-state=>'disabled'); } __END__

        I'm not really a human, but I play one on earth. flash japh
Re: Perl::TK - fileevent and script execution theory
by crabbdean (Pilgrim) on Mar 14, 2004 at 12:19 UTC
    Okay, thanks merlyn and zentara in this node for their responses, it very much helped. Although what I was attempting to achieve could be seen in the TK::ExecuteCommand module I wanted to know and write how to output an open stream to a widget (notably a text Widget). By open stream I mean STDOUT or any open pipe, or Filehandle. The rest you can read in the responses and discussion of this node.

    Outputting to a text widget is all fine and dandy if what you are reading and outputting is small, because the population of the data to gui is relatively fast. But try executing a script or outputting a large amount of data like a listing of your entire hard drive and your gui freezes until it finishes the read. For all intensive purpose your gui looks like its crapped itself, while its off busily working in the background. The event driven nature of TK means it will process this event before continuing and updating the display or the next event.

    The TK::fileevent module is designed to execute a callback when a FileHandle becomes either writable or readable. Great sounds good!! Although, my problem came in the implementation of this, which you can read about in this node. Finally here is the solution below. The "$widget->idletasks" method helps with the updating of the display when the widget changes or in this case is populated with more data, which fixes the apparant freezing problem. Calling "idletasks" for the MainWindow or the text widget within the callback both worked although on the Text Widget would require less redraw and is most likely faster, hence the solution below. I found trying to call "idletasks" within the MainLoop didn't work.

    Below is the final solution. Hope that helps.

    Dean
    The Funkster of Mirth
    Programming these days takes more than a lone avenger with a compiler. - sam
    RFC1149: A Standard for the Transmission of IP Datagrams on Avian Carriers
    #!/usr/bin/perl use warnings; use strict; use Tk; use IO::Handle; open(CHILD, "e:\\cygwin\\bin\\ls -laR c: |") or die "Can't open: $!"; my $mw = new MainWindow; my $t = $mw->Scrolled("Text", -width => 80, -height => 25, -wrap => 'n +one'); $t->pack(-expand => 1); CHILD->autoflush(1); $mw->fileevent('CHILD', 'readable', [\&fill_text_widget, $mw]); MainLoop; sub fill_text_widget { my ($widget) = @_; if (eof(CHILD)) { $widget->fileevent('CHILD', "readable", undef); # cancel bindi +ng return ; } if (sysread ('CHILD', $_, 1028)) { $t->insert('end', $_); # Append the data read $t->yview('end'); $widget->bell; } else { # sysread returned undef. Problem with file $widget->fileevent('CHILD', "readable", undef); # cancel bindi +ng } $t->idletasks; }
      I've noticed in the above code that instead of exiting at the eof(CHILD) section it exists at the bottom "else" statement. All exiting commands I'm wanting to execute such as cleanup or "destroys" I'm putting here, which seems illogical. I would have thought it would exit at the "eof". Is there a reason for this?

      Update: It would seem more correct to put the whole bottom "sysread" if statement as and "else" to the "eof". See below.
      if (eof($handle)) { $widget->fileevent($handle, "readable", undef); # cancel bindi +ng return ; } else { if (sysread ($handle, $_, 128)) { $tx->insert('end', $_); # Append the data read $tx->yview('end'); } else { $tx->insert('end', times); $tx->yview('end'); $widget->fileevent($handle, "readable", undef); # cancel b +inding return; } }

      Dean
      The Funkster of Mirth
      Programming these days takes more than a lone avenger with a compiler. - sam
      RFC1149: A Standard for the Transmission of IP Datagrams on Avian Carriers

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://336098]
Approved by matija
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others musing on the Monastery: (8)
As of 2014-12-28 17:04 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    Is guessing a good strategy for surviving in the IT business?





    Results (182 votes), past polls