这是对is whenever signal() in react block order dependent? 的跟进问题。
以下使用默认调度程序$*SCHEDULER
的代码使用户可以在以下事件循环中按CTRL-C来立即退出:
use v6;
my %scheduler;
my $use-default-scheduler = True;
if $use-default-scheduler {
%scheduler = scheduler => $*SCHEDULER;
}
react {
whenever signal(SIGINT, |%scheduler) {
say "Got signal";
exit;
}
whenever Supply.from-list($*IN.lines, |%scheduler) {
say "Got line";
exit if $++ == 1 ;
}
}
我对在同一react
循环中使用两个不同的线程调度程序会发生什么感兴趣?如果我使用默认线程调度程序Supply.from-list()
而不是$*SCHEDULER
,请在上述代码中设置$use-default-scheduler = False
。现在,用户无法通过按react
来立即退出CTRL-C
块。如果他按CTRL-C
,程序将一直挂起,直到按Enter。
那么这里实际发生了什么? react
一次只关注一个事件循环吗? (我在这里想象两个事件循环,一个用于SIGINT信号的第一个whenever
中使用的默认调度程序,另一个用于$*IN.lines
电源中的事件循环)。因此,react
现在专注于from-list()
的{{1}}调度程序,但是在此事件循环中以某种方式忽略了SIGINT吗?因此,按$*IN.lines
不会更改CTRL-C
块的状态吗?
答案 0 :(得分:9)
要查看实际发生的情况,让我们重新编写程序以涵盖react
的工作。我将忽略许多细节,这些细节对手头的问题并不太重要。
为了使事情更紧凑,我将重写所提供程序的这一部分:
react {
whenever signal(SIGINT, |%scheduler) {
say "Got signal";
exit;
}
whenever Supply.from-list($*IN.lines, |%scheduler) {
say "Got line";
exit if $++ == 1 ;
}
}
首先,react { ... }
确实与await supply { ... }
类似-也就是说,它点击了supply { }
块并await
的结尾。
await supply {
whenever signal(SIGINT, |%scheduler) {
say "Got signal";
exit;
}
whenever Supply.from-list($*IN.lines, |%scheduler) {
say "Got line";
exit if $++ == 1 ;
}
}
但是supply
块是什么? supply
(react
这样)的本质是:
Lock::Async
)Promise
来实现这一点,因为我们只想要react
部分;真实的东西会产生结果{{ 1}},我们可以将Supply
的值放入)emit
中的订阅)因此,我们可以将程序重写为如下形式:
SetHash
请注意,这里的调度程序或事件循环什么都没有。 await do {
# Concurency control
my $lock = Lock::Async.new;
# Completion/error conveyance
my $done = Promise.new;
# What's active?
my %active is SetHash;
# An implementation a bit like that behind the `whenever` keyword, but with
# plenty of things that don't matter for this question missing.
sub whenever-impl(Supply $s, &block) {
# Tap the Supply
my $tap;
$s.tap:
# When it gets tapped, add the tap to our active set.
tap => {
$tap = $_;
%active{$_} = True;
},
# Run the handler for any events
{ $lock.protect: { block($_) } },
# When this one is done, remove it from the %active list; if it's
# the last thing, we're done overall.
done => {
$lock.protect: {
%active{$tap}:delete;
$done.keep() unless %active;
}
},
# If there's an async error, close all taps and pass it along.
quit => {
$lock.protect: -> $err {
.close for %active.keys;
$done.quit($err);
}
}
}
# We hold the lock while doing initial setup, so you can rely on having
# done all initialization before processing a first message.
$lock.protect: {
whenever-impl signal(SIGINT, |%scheduler), {
say "Got signal";
exit;
}
whenever-impl Supply.from-list($*IN.lines, |%scheduler), {
say "Got line";
exit if $++ == 1 ;
}
}
$done
}
或supply
不在乎消息的来源,它只是在乎通过react
实施的自身完整性。还要注意,它也没有引入任何并发:它实际上只是一个并发控制结构。
通常,人们将Lock::Async
和supply
与数据源一起使用,react
在其中,并立即获得控制权。然后,我们继续设置其他tap
块,退出设置阶段,并且该锁可用于我们收到的任何消息。这种行为是您通常遇到的所有耗材都会得到的。 whenever
就是这种情况。当您给signal(...)
一个明确的调度程序,并传入Supply.from-list(...)
时,也是这种情况;在这种情况下,它会调度从池中的$*SCHEDULER
读取的循环,并立即交出控制权。
当我们遇到不是那样的行为时,问题就来了。如果我们点击$*IN
,则默认情况下是从当前线程上的Supply.from-list($*IN.lines)
进行读取,以生成$*IN
的值,因为emit
使用Supply.from-list
作为其值。默认。那怎么办?只需运行要求立即运行的代码即可!
但是,这又给我们带来了一个谜。鉴于CurrentThreadScheduler
不是可重入的,那么如果我们:
Lock::Async
上的tap
,它会同步运行并尝试Supply.from-list(...)
的值然后,我们将陷入僵局,因为我们正试图获取一个已经由我们持有的不可重入的锁。的确,如果您在此处运行该程序的desugar,这正是发生的情况:它挂起了。但是,原始代码不会挂起;它的表现有点尴尬。有什么作用?
真正的实现之一是在设置阶段检测这种情况;然后继续执行,并在设置阶段完成后继续执行。这意味着我们可以做类似的事情:
emit
解决问题。我不会在这里重现那个的乐趣,但是应该充分狡猾地使用my $primes = supply {
.emit for ^Inf .grep(*.is-prime);
}
react {
whenever $primes { .say }
whenever Promise.in(3) { done }
}
/ gather
。