2017-07-28 90 views
1

在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

你有权访问'$ callback'吗? – simbabque

+0

@simbabque他们(我们?)写入''回调'转换成'$ callback'的匿名子...? – zdim

+0

有https://metacpan.org/pod/Return::MultiLevel可能会有所帮助,但只要'my_fixed_handler'中的'count'是词法,这不会有太大的帮助。每次你调用'my_fixed_handler'时,它都会重新开始,所以将它封装在返回的东西中会每次给你'1'。 '$ callback'应该如何打破'while(1)'循环? – simbabque

回答

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 

首先笔者会继续打印,直到它填补了缓冲。 &dagger;然后,作为读者得到一行,作者提出另一个(或两个,如果打印的长度不同)等,如果这是好的,删除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的意见。


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

+0

我无法添加投票(尚未拥有足够的权限),但感谢这个伟大的答案! – Hochstenbach

+0

@Hochstenbach谢谢,很高兴你喜欢它:)。如果有问题,请告诉我。 – zdim