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

Dear Monks, In my Perl/Tk code, I have a auto-refresh functionality which calls a function whose job is to update the canvas with the latest network topology. I am using repeat:
$autorefresh_id = $root->repeat($rinterval*60000,\&refresh_func);
$rinterval is the auto-refresh interval (in minutes) set by the user. The question is this: the refresh_func() could take some time to run depending upon the size of the network. If a user sets the refresh interval ($rinterval) to a time less than the time it takes to refresh the display, I assume that it would lead to issues since the same variables will get overwritten if refresh_func runs before the preceding function has returned. What would be the proper mechanism to handle this? Thank you in advance...

Replies are listed 'Best First'.
Re: Tk::repeat function
by zentara (Cardinal) on Feb 09, 2012 at 15:26 UTC
    What would be the proper mechanism to handle this?

    You are running into a problem called "blocking the eventloop". The easiest way to solve it, is to run the &refresh_func in a thread. There are many details you must observe to use Tk with threads, and this assumes you have no Tk code in the refresh_func().

    You can have a few shared variables, to pass information back and forth between your thread and main. The idea would be to let the thread update the network data as fast as it can, and if the user sets the refresh rate too low, he just keeps getting repeat data shown, until the thread is done. You can google for many "perl/tk threads" examples. Here is a an extra clever Tk Canvas threaded program, which uses Tk::Trace's tracevariable.

    #!/usr/bin/perl use warnings; use strict; use threads; use threads::shared; my $v:shared=0; my $thread = threads->new( \&launch_thread )->detach; package Tk; use Tk::Trace; package main; use Tk; use constant PI => 3.1415926; my $mw = MainWindow->new; $mw->fontCreate('medium', -family=>'courier', -weight=>'bold', -size=>int(-14*14/10)); my $c = $mw->Canvas( -width => 200, -height => 110, -bd => 2, -relief => 'sunken', -background => 'black', )->pack; $c->createLine(100,100,10,100, -tags => ['needle'], -arrow => 'last', -width => 5, -fill => 'hotpink', ); my $gauge = $c->createArc( 10,10, 190,190, -start => 0, -style => 'arc', -width => 5, -extent => 180, -outline => 'skyblue', -tags => ['gauge'], ); my $hub = $c->createArc( 90,95, 110,115, -start => 0, -extent => 180, -fill => 'lightgreen', -tags => ['hub'], ); $mw->traceVariable(\$v, 'w' => [\&update_meter]); $mw->bind('<Motion>' => sub{ $v += 1 }); $mw->repeat(50,sub{ $v-- }); my $text = $c->createText( 100,50, -text => $v, -font => 'medium', -fill => 'yellow', -anchor => 's', -tags => ['text'] ); $c->raise('needle','text'); $c->raise('hub','needle'); MainLoop; sub update_meter { my($index, $value) = @_; if($value <= 0){$value = 0 } if($value >= 100){$value = 100} my $pos = $value / 100; my $x = 100.0 - 90.0 * (cos( $pos * PI )); my $y = 100.0 - 90.0 * (sin( $pos * PI )); $c->coords('needle', 100,100, $x, $y); $c->itemconfigure($text,-text => $value); return $value; } sub launch_thread{ while(1){ $v = 50 + int rand 50; print "$v\n"; select(undef,undef,undef,.1); } }

    I'm not really a human, but I play one on earth.
    Old Perl Programmer Haiku ................... flash japh
      Thanks you. The problem I have is that I cannot run my refresh_func() in a thread since it is using net::ssh:expect and this module is not thread safe. So I am kind of stuck.
Re: Tk::repeat function
by thundergnat (Deacon) on Feb 09, 2012 at 15:22 UTC

    In cases like this, I generally use some kind of flag to prevent more than one instance from running at a time.

    my $is_refreshing = 0; $autorefresh_id = $root->repeat($rinterval*60000,\&refresh_func); sub refresh_func { return if $is_refreshing; $is_refreshing = 1; ... #long running routine ... $is_refreshing = 0; }

    You just need to make sure to reset the sentinal flag anywhere the routine can exit.

Re: Tk::repeat function
by kcott (Archbishop) on Feb 09, 2012 at 15:40 UTC

    I'd probably use some sort of flag to indicate whether refreshing is currently happening. Perhaps along the lines of:

    { my $is_refreshing = 0; sub refresh_func { return if $is_refreshing; $is_refreshing = 1; # refresh code here $is_refreshing = 0; return; } }

    You'll probably want to check for exceptions such as comms failures, timeouts, etc. which also reset the flag before exiting.

    Depending on your version of Perl and how you've actually written your refresh_func() code, $is_refreshing might be better declared as a state variable:

    sub refresh_func { state $is_refreshing = 0; return if $is_refreshing; $is_refreshing = 1; # refresh code here $is_refreshing = 0; return; }

    -- Ken

Re: Tk::repeat function
by Anonymous Monk on Feb 09, 2012 at 15:34 UTC
    For this type of thing I usually use the cancel method at the start of the refresh sub, then recreate the timer at the end of the sub. Seems to work pretty well for me.
    sub refresh_func{ $autorefresh_id->cancel(); #cancel the timer #.... do stuff ... $autorefresh_id = $root->repeat($rinterval*60000,\&refresh_func); +#recreate the timer }