将回调转换为流

时间:2017-07-28 08:27:06

标签: perl inversion-of-control

在Perl中,如何将需要回调的函数转换为返回结果流的新函数?

图片我有一个固定的功能,我无法改变:

sub my_fixed_handler {
    my $callback = shift;

    my $count = 1;
    while(1) {
       $callback->($count++);
    }
}

要打印所有数字,我可以轻松编写此代码:

my_fixed_handler( sub {
    my $num = shift;
    print "...$num\n";
});

但现在我需要另一个基于my_fixed_handler的函数,它只会返回一个计算步骤的结果:

my $stream = my_wrapper( my_fixer_hander( ... ) ) ;
$stream->next;  # 1
$stream->next;  # 2

这可能吗?

1 个答案:

答案 0 :(得分:0)

使用管道在满员时阻塞的事实:在分叉的过程中运行fixed_handler,回调通过管道写回父管。父进程在读取后​​进行处理时,管道被阻止(如果已满)且写入程序正在等待。为方便起见,请写一个额外的空字符串来填充管道。

use warnings;
use strict;
use feature 'say';

sub fixed_handler {
    my $callback = shift;
    #state $count = 1; # would solve the problem
    my $count = 1;
    for (1..4) { $callback->($count++) }   
}

pipe my $reader, my $writer  or die "Can't open pipe: $!";
$writer->autoflush(1);
$reader->autoflush(1);

my $fill_buff = ' ' x 100_000;  # (64_656 - 3); # see text

my $iter = sub { 
    my $data = shift; 
    say "\twrite on pipe ... ($data)";
    say $writer $data;
    say $writer $fill_buff;     # (over)fill the buffer
};

my $pid = fork // die "Can't fork: $!";  #/

if ($pid == 0) {
    close $reader;
    fixed_handler($iter);
    close $writer;
    exit;
}

close $writer;
say "Parent: started kid $pid";

while (my $recd = <$reader>) {
    next if $recd !~ /\S/;      # throw out the filler
    chomp $recd;
    say "got: $recd";
    sleep 1;
}

my $gone = waitpid $pid, 0;
if    ($gone > 0) { say "Child $gone exited with: $?" }
elsif ($gone < 0) { say "No such process: $gone" }

输出

Parent: started kid 13555
        write on pipe ... (1)
got: 1
        write on pipe ... (2)
got: 2
        write on pipe ... (3)
got: 3
        write on pipe ... (4)
got: 4
Child 13555 exited with: 0

首先,作者会继续打印,直到它填满缓冲区。然后,当读者得到一行时,作者会放另一行(或两个,如果打印的长度不同)等。如果没有问题,请删除say $writer $fill_buff;。然后在输出中我们首先看到所有write on pipe行,然后父母的打印出去。现在常见的缓冲区大小是64K。

然而,我们被告知file_handler的每一步都需要时间,所以我们要等待数千个这样的步骤才能在父进程中开始处理(取决于每次写入的大小),直到缓冲区为止填写和作者开始在每次阅读时被阻止。

一种方法是写一个额外的字符串,足够长以填充缓冲区,并在阅读器中将其关闭。虽然我发现这个确切的长度我觉得很挑剔。例如,

中程序中找到的缓冲区
my $cnt; while (1) { ++$cnt; print $writer ' '; print "\r$cnt" } # reader sleeps

与命令行上的类似方式不同。即便如此,我仍然(有时)得到“双重写作”。虽然 可能不是节目制作者,但我选择100K以确保填写它。

有关缓冲区大小的讨论,请参阅this post

另一种方法可能是使用IO::Handle::setvbuf设置管道的缓冲区大小。但是,我遇到了“未在此架构上实现”(在生产机器上),因此我不会考虑这一点。

缓冲混乱当然会减慢通信速度。

这是根据melpomene的评论实现的。

对于“缓冲区”,我指的是管道缓冲区(如果在另一侧没有读取数据,则在管道阻塞之前写入的数据量)。这是其他涉及的缓冲区,但在这里并不相关。