问题
I wrote this test program:
await Promise.anyof(
Promise.allof((^5).map: {start { sleep 10; say "done $_" } }),
Promise.in(5).then: { say 'ouch' }
);
sleep 10;
When the second promise times out it prints 'ouch' and the await exits, but the first promise's code block is still running. After five more seconds its five processes end and print 'done':
$ ./test1.p6
ouch
done 0
done 1
done 2
done 3
done 4
I tried to terminate the first promise assigning it to a variable and then calling the .break
method from the second promise, but it doesn't work.
Is there a way to kill the first promise and the other five sub-promises it started?
回答1:
You have to somehow tell the process that it doesn't have to finish.
my $cancel = Cancellation.new;
await Promise.anyof(
Promise.allof(
(^5).map: {
last if $cancel.cancelled;
start {
sleep 10;
say "done $_" unless $cancel.cancelled
}
}
),
Promise.in(5).then: {
$cancel.cancel;
say 'ouch'
}
);
If you want something like Promise.in()
that can be cancelled, let's start by looking at the existing code.
method in(Promise:U: $seconds, :$scheduler = $*SCHEDULER) {
my $p := self.new(:$scheduler);
my $vow := $p.vow;
$scheduler.cue({ $vow.keep(True) }, :in($seconds));
$p
}
Note that the result of $scheduler.cue
is a Cancellation.
I am just going to wrap a Promise, and a Cancellation in a class for simplicity.
(I don't want to reimplement every method).
class Cancellable-Timer {
has Promise $.Promise;
has $!vow;
has Cancellation $!cancel;
method !SET-SELF ( $!promise, $!vow, $!cancel ){
self
}
method in (::?CLASS:U: $seconds, :$scheduler = $*SCHEDULER) {
my $p := Promise.new(:$scheduler);
my $vow := $p.vow;
my $cancel = $scheduler.cue({ $vow.keep(True) }, :in($seconds));
self.bless!SET-SELF($p,$vow,$cancel);
}
method cancel ( --> Nil ) {
# potential concurrency problem
if $!Promise.status == Planned {
$!cancel.cancel; # cancel the timer
$!vow.break("cancelled"); # break the Promise
}
}
method cancelled () {
# Ignore any concurrency problems by using the Promise
# as the sole source of truth.
$!Promise.status ~~ PromiseStatus::Broken
}
}
my $timer = Cancellable-Timer.in(1);
my $say = $timer.Promise.then: *.say;
Promise.in(0.1).then: {$timer.cancel};
await $say;
Note that the above class is just a rough first draft.
回答2:
Try with whenever:
$ perl6 -e '
react {
for ^5 -> $num {
whenever start { sleep 10 } {
say "done $num"
}
}
whenever Promise.in: 5 {
say "ouch";
done
}
}
' ouch
来源:https://stackoverflow.com/questions/52955919/is-it-possible-to-terminate-a-promises-code-block-from-another-promise