Jnthn 回答了为什么 $*IN.lines
会在 react
块中阻塞的问题。没有解释的是,在开始阻塞之前,whenever
到底做了什么?
react {
whenever $*IN.lines { .say }
}
观察 whenever
块的语法,我们可以看到,whenever
取一个变量后紧跟着一个块。唯一可以定义这样的结构的地方是 Grammar.nqp。
rule statement_control:sym<whenever> {
<sym><.kok>
[
|| <?{
nqp::getcomp('perl6').language_version eq '6.c'
|| $*WHENEVER_COUNT >= 0
}>
|| <.typed_panic('X::Comp::WheneverOutOfScope')>
]
{ $*WHENEVER_COUNT++ }
<xblock($PBLOCK_REQUIRED_TOPIC)>
}
这里的语法只是检查一些东西,而没有实际生成任何代码。所以我们前往 Actions.nqp。
method statement_control:sym<whenever>($/) {
my $xblock := $<xblock>.ast;
make QAST::Op.new(
:op<call>, :name<&WHENEVER>, :node($/),
$xblock[0], block_closure($xblock[1])
);
}
我们在 Supply.pm6
中发现,whenever
块被转换为对 sub WHENEVER
的调用。
sub WHENEVER(Supply() $supply, &block) {
就这样。只要 Any
是该类型的父类,那么 whenever
块都会接受 Any
类型的第一个参数并调用 .Supply
。在 $*IN
的情况下,该类型通常是 IO::Handle.lines 所返回的类型。
Seq.new(self!LINES-ITERATOR($close))
要把一个 Seq 变成一个 Supply Any.Supply
调用 self.list.Supply
。在这个相当长的方法查找链中(这不可能很快),没有任何地方可以找到线程。如果我们想解决这个问题,我们需要偷偷在 $*IN.lines
中加入一个 Channel
,它正是这样做的。
$*IN.^can('lines')[1].wrap(my method {
my $channel = Channel.new;
start {
for callsame() {
last if $channel.closed;
$channel.send($_)
}
LEAVE $channel.close unless $channel.closed;
}
$channel
});
或者如果我们想显式的。
use Concurrent::Channelify;
react {
whenever signal(SIGINT) {
say "Got signal";
exit;
}
whenever $*IN.lines⇒ {
say "got line";
}
}
我们已经用 ⚛
来表示原子操作。也许用 prefix:<∥>
来表示并发是有意义的。总之,我们又一次走了狗屎运,Rakudo 在 Perl 6 中实现了(大部分),所以我们可以随时找到我们需要戳它的地方。
by gfldex.