无论何时何地

Whatever whenever does

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.

comments powered by Disqus