Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

print sth every second, and also sleep 10 seconds very 5 seconds using react ... whenever in Perl 6?

Tags:

raku

I want to print the current time every second, and also want to sleep 10 seconds very 5 seconds:

react {
    whenever Supply.interval(1) {
        say DateTime.now.posix;
    }

    whenever Supply.interval(5) {
        sleep 10;
        say 'Sleep Done';
    }

    whenever signal(SIGINT) {
        say "Done.";
        done;
    }
}

the output is not what i wanted:

1542371045
Sleep Done
1542371055
Sleep Done
1542371065
Sleep Done
1542371075
Done.
...

what i want is this:

1542371045
1542371046
1542371047
1542371048
1542371049 
Sleep Done
1542371059
1542371060
1542371061  
1542371062 
1542371063         
Sleep Done
Done.

Don't know much about Promise, Supply... about Raku, is this possible?

like image 516
chenyf Avatar asked Nov 16 '18 05:11

chenyf


3 Answers

One mistake you are making is that you are assuming that supplies will lose values, or you are assuming they will stop generating values while the react is blocked. They won't.
They keep generating values.

You should also try to have the code in a whenever run for as short of a time as possible.
(Pretend it is a CPU interrupt handler.)
There may be some exceptions to this rule, particularly for supply blocks.

Using the structure that you provided, this is one way to achieve what you want:

 react {
    # Are we ignoring the interval(1) values?
    my Bool:D $ignore = False;

    # The sleeping status of interval(5).
    my Promise:D $sleep .= kept;

    whenever Supply.interval(1) {
        # Skip if it is supposed to be blocked.
        next if $ignore;

        say DateTime.now.posix;
    }

    # First one runs immediately, so skip it.
    whenever Supply.interval(5).skip {
        # Don't run while the “sleep” is pending.
        next unless $sleep.status; # Planned

        if $ignore {
            $ignore = False;
            say 'Sleep Done';
        } else {
            $ignore = True;
            # Must be less than the multiple of 5 we want
            # otherwise there may be a race condition.
            $sleep = Promise.in(9);
        }
    }

    whenever signal(SIGINT) {
        say "Done.";
        done;
    }
}

That isn't very clear.
How about we just use .rotor instead, to skip every third interval of 5?

react {
    my Bool:D $ignore = True;

    # Note that first one runs immediately. (no .skip)
    # We also want it to always be a few milliseconds before
    # the other Supply, so we put it first.
    # (Should have done that with the previous example as well.)
    whenever Supply.interval(5).rotor(1, 1 => 1) {
        $ignore = !$ignore;
    }

    whenever Supply.interval(1) {
        next if $ignore;

        say DateTime.now.posix;
    }

    whenever signal(SIGINT) {
        say "Done.";
        done;
    }
}

While we are at it, why not just use .rotor on the .interval(1) Supply?

react {
    whenever Supply.interval(1).rotor(1 xx 4, 1 => 10) {
        say DateTime.now.posix;
    }

    whenever signal(SIGINT) {
        say "Done.";
        done;
    }
}

Note that we can't just use 5 => 10 because that batches them up, and we want them to be run singly.


Note that .grep also works on Supplys, so we could have used that instead to check the $ignored value.

react {
    my Bool:D $ignore = True;

    whenever Supply.interval(5).rotor(1, 1 => 1) {
        $ignore = !$ignore;
    }

    whenever Supply.interval(1).grep({ !$ignore }) {
        say DateTime.now.posix;
    }

    whenever signal(SIGINT) {
        say "Done.";
        done;
    }
}
like image 110
Brad Gilbert Avatar answered Oct 07 '22 11:10

Brad Gilbert


Maybe this can work:

loop {
    react {
        whenever Supply.interval(1) {
            say DateTime.now.posix;
        }

        whenever Promise.in(5) {
            done;
        }

        whenever signal(SIGINT) {
            say "Done.";
            done;
        }
    }
    sleep 10;
}

The output is:

1542347961
1542347962
1542347963
1542347964
1542347965
1542347976 # <- 10s
1542347977
1542347978
1542347979
1542347980
1542347991 # <- 10s
like image 35
chenyf Avatar answered Oct 07 '22 13:10

chenyf


Depending on exactly what else was needed, I'd probably write it something like this:

react {
    sub sequence() {
        whenever Supply.interval(1).head(5) {
            say DateTime.now.posix;
            LAST whenever Promise.in(10) {
                say "Sleep done";
                sequence();
            }
        }
    }
    sequence();
}   

Which gives output like this:

1542395158
1542395159
1542395160
1542395161
1542395162
Sleep done
1542395172
1542395173
1542395174
1542395175
1542395176
Sleep done
1542395186
1542395187
1542395188
...

This will make absolutely sure you get 5 ticks out between the 10s pauses; doing it with two separate interval supplies - as in many solutions here - will not give any strict guarantees of that, and could miss a tick now and then. (One that doesn't is the cute one with rotor, which is a good bet if you don't need to actually print the "sleep done" thing). It's also free of state (variables) and conditions, which is rather nice.

While this looks like it might be recursive, since whenever is an asynchronous looping construct, it will not actually build up a call stack at all.

It's also fully built of asynchronous constructs, and so in Perl 6.d will not - if the react is triggered on the thread pool - ever block a real OS thread. So you could have thousands of these active. By contrast, sleep will block a real thread, which is what sleep traditionally would be expected to do, but isn't such a good fit if otherwise dealing with asynchronous constructs.

like image 11
Jonathan Worthington Avatar answered Oct 07 '22 11:10

Jonathan Worthington