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

♀尐吖头ヾ 提交于 2019-12-08 15:17:07

问题


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?


回答1:


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.




回答2:


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;
    }
}



回答3:


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



回答4:


The thing is the two Supplies are effectively running in different threads so don't interact with each other. Your sleep only puts the thread it's in to sleep (and then the fact it's a 5 second interval creates another sleep anyway).

To achieve the result you're looking for I went with this which uses the single 1 second interval and a couple of flags.

react { 
    whenever Supply.interval(1) { 
        state $slept = False;  
        state $count = 0;
        if $count >= 0 { 
            if $slept { 
                say "Sleep Done"; 
                $slept = False 
            } 
            say DateTime.now.posix; 
        } 
        $count++; 
        if ( $count == 5 ) { 
            $count = -9; 
            $slept = True
        }
    }
    whenever signal(SIGINT) {
        say "Done.";
        done;
    }
}

Note that we have to use state variables because the whenever block is effectively executed in it's own thread each second. The state variables allow us to keep track of the current situation.

If it was running on a smaller interval I would maybe think about using atomic ints instead of normal ones (in case the code was executed while it was still running) but that block should never take more than a second to execute so I don't think it's a problem.




回答5:


Because only one whenever will be executing at any time, the sleep in there will be halting all handling of things to react to. The easiest way to achieve what you want, is to do the sleep as an asynchronous job by wrapping the code of that whenever into a start block.

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

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

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

This gives the desired output, as far as I can see.



来源:https://stackoverflow.com/questions/53331761/print-sth-every-second-and-also-sleep-10-seconds-very-5-seconds-using-react

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!