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

I have a perl script which presents the user with a menu. Every few minutes the user will select an option, a subroutine is called, and then back to the menu. This is all well and good and works fine. I do, however, want to occasionally (maybe once every 5 minutes for example) run a subroutine without any input from the user.

I have looked at a few different timers/schedulers in cpan but I can't seem to find anything that fits this situation in a elegant fashion. I now turn to you all-knowing purveyors of wisdom!

Replies are listed 'Best First'.
Re: Run subroutine occasionally
by tybalt89 (Monsignor) on Mar 01, 2022 at 02:12 UTC

    Just for fun (and because I haven't done a ReadKey in a while), here's my guess at a structure for what you are asking.

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11141714 use warnings; use Term::ReadKey; use Time::HiRes qw( time ); use List::Util qw( max ); $| = 1; my $n = my @menuitems = qw( one two three four ); my ($w, $h) = GetTerminalSize; my $interval = 10; my $due = time + $interval; my $result = eval { ReadMode 'cbreak'; while( 1 ) { print "\e[H\e[J", "\n" x ($h - @menuitems - 5), "enter number for choice:\n\n"; print "$_ : $menuitems[ $_ - 1 ]\n" for 1 .. @menuitems; print "\nnumber : "; my $delta = max 0.001, $due - time; local $_ = ReadKey $delta; if( not defined $_ ) { print("running occasional program every $interval seconds"); sleep 1; $due = time + $interval; } elsif( /[1-$n]/ ) { print("running $menuitems[$_ - 1]"); sleep 1; } elsif( /[qQ\e]/ ) { last; } else { print("bad key, try again"); sleep 1; } } } || $@; ReadMode 'restore'; print "$result\n";

    Of course, replace the sleeps with your subs, and more work would be required if you have more than 9 menu items.

Re: Run subroutine occasionally
by tybalt89 (Monsignor) on Feb 28, 2022 at 22:56 UTC

    perl/Tk
    Term::ReadKey has a timed read feature
    Any of the async packages

Re: Run subroutine occasionally
by haukex (Archbishop) on Feb 28, 2022 at 22:59 UTC

    You haven't told asus much about what this sub is doing, so I'll just throw the easiest answer out there: you could do that in a separate process. However, I am guessing that you're asking the question because the code needs to share some state. Two other solutions that come to mind are that prompting modules like IO::Prompter have timeout options, or for something a little more low-level, alarm and a custom %SIGnal handler - but be aware the latter has some limitations on Win and that SIGALRM may also be used by other things (e.g. sleep). In general, I think it'd be best if you coud tell us more about your application (OS, modules, maybe some sample code, etc.) and then we could provide better answers.

Re: Run subroutine occasionally
by Marshall (Canon) on Mar 01, 2022 at 00:11 UTC
    consider: $MW->repeat($MILLISECOND_DELAY, \&someSub);
    runs someSub repeatedly.

    Of course, sub someSub{} should run "quickly" or the GUI will "freeze".

    Update: I re-read the question and perhaps I made an incorrect about this being a Tk GUI? If so, the OP needs to clarify.

    Update with Code for a GUI:

    use strict; use warnings; use Tk; # quick hack # reports the last function button pressed # every 3 seconds # Doesn't start reporting until first button is pressed. my @buttons = (["function1", \&func1], ["functoin2", \&func2], ["function3", \&func3],); my $last_function_executed =0; sub func1{ print "this is function 1\n"; $last_function_executed = 1; } sub func2{ print "this is function 2\n"; $last_function_executed = 2; } sub func3{ print "this is function 3\n"; $last_function_executed = 3; } sub report_last_func{ return unless $last_function_executed; # no button pressed yet! print "most recent operation was: $last_function_executed\n"; } my $MW = MainWindow->new(); foreach my $button_ref (@buttons) # create buttons { my ($text,$subref) = @$button_ref; $MW->Button(-text=> $text, -command => $subref)-> pack; } $MW->repeat(3*1000, \&report_last_func); MainLoop;
Re: Run subroutine occasionally
by stevieb (Canon) on Mar 01, 2022 at 18:59 UTC

    My Async::Event::Interval does this. It takes a callback as a parameter, and runs it at a given interval (in seconds). The callback is run in a separate process.

    Example:

    use warnings; use strict; use feature 'say'; use Async::Event::Interval; my $delayed_event = Async::Event::Interval->new(5, \&interval_sub); $delayed_event->start; while (1) { say time; sleep 1; } sub interval_sub { say "Running interval sub"; }

    Output:

    1646160956 1646160957 1646160958 1646160959 1646160960 Running interval sub 1646160961 1646160962 1646160963 1646160964 1646160965 Running interval sub 1646160966 1646160967 1646160968 1646160969 1646160970 ^C

    An interval (first parameter to the new() method) of 0 (zero) will only run the routine once, but you can run it again at any time by calling $delayed_event->start if $delayed_event->waiting;. Floating point intervals are also supported if one needs such granularity.

Re: Run subroutine occasionally
by cavac (Prior) on Mar 02, 2022 at 11:45 UTC

    You have not told us any details, so it's hard to guess what user interface you are using and what you are trying to do.

    If you want to make sure your "subroutine" doesn't block the UI, running it in a different program altogether with interprocess messaging might be an option. This would also allow for the cyclic function to continue running without the UI open.

    All this depends on your requirements and all the details you haven't provided, though. As for me, i usually use Net::Clacks for that stuff (of course, as the author of that module, i'm a bit biased). There are examples of a simple chat system, complete with chatbot and clock bot, if you are interested. Explanation is provided in this (slightly outdated) post: Interprocess messaging with Net::Clacks

    perl -e 'use Crypt::Digest::SHA256 qw[sha256_hex]; print substr(sha256_hex("the Answer To Life, The Universe And Everything"), 6, 2), "\n";'
Re: Run subroutine occasionally
by marioroy (Prior) on Mar 02, 2022 at 19:56 UTC

    The yield function in MCE::Child (ditto for MCE::Hobo) retains interval periods; i.e. do something at/near every N fractional seconds. It works quite well, even among many workers.

    use strict; use warnings; use MCE::Child; use Time::HiRes qw(sleep time); STDOUT->autoflush(1); # do something periodically in the background my $child = MCE::Child->create(sub { while (1) { MCE::Child->yield(300.0); print "Inside child ", time, $/; } }); # application code print "Parent", $/; # reap child $child->kill; $child->join;

    Demonstration

    Notice the output for the next demonstration where the child displays output every 0.2 seconds, even simulating work. It does a delta behind the scene from the last yield statement in order to retain near/exact intervals.

    use strict; use warnings; use MCE::Child; use Time::HiRes qw(sleep time); STDOUT->autoflush(1); my $child = MCE::Child->create(sub { while (1) { MCE::Child->yield(0.200); print "Inside child ", time, $/; sleep 0.1; # simulate work } }); for (1..5) { print "Parent ", $_, $/; sleep 1; } $child->kill; $child->join;

    Output

    $ perl demo.pl Parent 1 Inside child 1646249559.98288 Inside child 1646249560.18285 Inside child 1646249560.38241 Inside child 1646249560.58239 Parent 2 Inside child 1646249560.78239 Inside child 1646249560.98283 Inside child 1646249561.18284 Inside child 1646249561.38284 Inside child 1646249561.58284 Inside child 1646249561.7825 Parent 3 Inside child 1646249561.98284 Inside child 1646249562.18284 Inside child 1646249562.38284 Inside child 1646249562.58284 Inside child 1646249562.78284 Parent 4 Inside child 1646249562.98284 Inside child 1646249563.18284 Inside child 1646249563.38284 Inside child 1646249563.58284 Inside child 1646249563.78283 Parent 5 Inside child 1646249563.98284 Inside child 1646249564.18284 Inside child 1646249564.38284 Inside child 1646249564.58284 Inside child 1646249564.78284

      The following is a demonstration spawning 2 child processes. Yield is an exclusive action causing other worker(s) to wait till the next interval period, as seen in the output.

      Demonstration 2

      use strict; use warnings; use MCE::Child; use Time::HiRes qw(sleep time); STDOUT->autoflush(1); sub background { my $id = shift; while (1) { MCE::Child->yield(0.200); print "Inside child #${id} ", time, $/; sleep 0.1; # simulate work } } MCE::Child->create(\&background, $_) for 1..2; for (1..5) { print "Parent ", $_, $/; sleep 1; } $_->kill->join for MCE::Child->list;

      Output

      $ perl demo2.pl Parent 1 Inside child #1 1646276375.98194 Inside child #2 1646276376.18194 Inside child #1 1646276376.38187 Inside child #2 1646276376.58187 Inside child #1 1646276376.78162 Parent 2 Inside child #2 1646276376.98187 Inside child #1 1646276377.18188 Inside child #2 1646276377.38187 Inside child #1 1646276377.58188 Inside child #2 1646276377.78186 Parent 3 Inside child #1 1646276377.98187 Inside child #2 1646276378.18187 Inside child #1 1646276378.38187 Inside child #2 1646276378.58187 Inside child #1 1646276378.78187 Parent 4 Inside child #2 1646276378.98187 Inside child #1 1646276379.18188 Inside child #2 1646276379.38187 Inside child #1 1646276379.58187 Inside child #2 1646276379.78187 Parent 5 Inside child #1 1646276379.98187 Inside child #2 1646276380.18187 Inside child #1 1646276380.38188 Inside child #2 1646276380.58187 Inside child #1 1646276380.78187