2017-07-28 3 views
1

Wie kann man in Perl eine Funktion transformieren, die Callbacks für eine neue Funktion benötigt, die einen Ergebnisstrom zurückgibt?Callbacks in einen Stream umwandeln

Bild I eine feste Funktion ändern kann ich nicht:

sub my_fixed_handler { 
    my $callback = shift; 

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

alles, was ich die eine Anzahl von Nummern drucken einfach diesen Code schreiben könnte:

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

Aber jetzt brauche ich eine andere Funktion auf der my_fixed_handler basierend auf, die nur das Ergebnis eines Berechnungsschritt zurück:

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

Ist das möglich?

+1

Haben Sie Zugang zu $ ​​Callback? – simbabque

+0

@simbabque Sie (wir?) Schreiben den anonymen Sub, der in '$ callback' verschoben ist ...? – zdim

+0

Es gibt https://metacpan.org/pod/Return::MultiLevel, was hilfreich sein könnte, aber solange '$ count' innerhalb von' my_fixed_handler' lexikalisch ist, wird es nicht viel helfen. Jedes Mal, wenn du 'my_fixed_handler' anrufst, wird es von vorn beginnen, also wird es jedes Mal, wenn du es wieder zurückbringst," 1 "geben. Wie soll der $ Callback die 'while (1)' Schleife durchbrechen? – simbabque

Antwort

0

Verwenden Sie die Tatsache, dass die Rohrleitung blockiert, wenn sie voll ist: Führen Sie die fixed_handler in einem gegabelten Prozess aus, wobei der Rückruf über eine Pipe zurück zum übergeordneten Element geschrieben wird. Während der Elternteil nach einem Lesen verarbeitet wird, wird die Pipe blockiert, wenn sie voll ist und der Schreiber wartet. Um dies zu erleichtern, schreiben Sie eine zusätzliche leere Zeichenfolge, um die Pipe zu füllen.

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" } 

Ausgabe

 
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 

Zuerst würde der Schriftsteller Druck halten, bis er den Puffer füllt. & Dolch; Dann, wenn der Leser eine Zeile erhält, setzt der Schreiber eine andere (oder zwei, wenn die Länge der Ausdrucke variiert) usw. Wenn dies in Ordnung ist, entfernen Sie say $writer $fill_buff;. Dann in der Ausgabe sehen wir alle write on pipe Zeilen zuerst, dann Eltern Drucke gehen. Eine übliche Puffergröße ist heutzutage 64K.

Allerdings wird uns gesagt, dass jeder Schritt von file_handler Zeit braucht und so würden wir auf Tausende solcher Schritte warten, bevor wir die Verarbeitung im Parent starten (abhängig von der Größe jedes Schreibvorgangs), bis der Puffer gefüllt ist und der Der Schreiber wird bei jedem Lesevorgang blockiert.

Ein Ausweg daraus ist, eine extra Zeichenfolge zu schreiben, lange genug, um den Puffer zu füllen, und es im Lesegerät zu verwerfen. Ich fand es schwierig, die genaue Länge dafür zu bekommen. Zum einen unterscheidet sich der im Programm gefundene Puffer von

my $cnt; while (1) { ++$cnt; print $writer ' '; print "\r$cnt" } # reader sleeps 

in ähnlicher Weise von dem in der Befehlszeile gefundenen. Auch damit bekomme ich (manchmal) "Doppelschreibversuche". Während , dass kein Show-Stopper sein kann, ging ich mit 100K, um sicherzustellen, dass es zu füllen.

Beispiele für Puffergrößen finden Sie unter this post.

Eine andere Möglichkeit besteht darin, die Puffergröße der Pipe unter Verwendung von IO::Handle::setvbuf festzulegen. Jedoch lief ich in "Nicht implementiert auf dieser Architektur" (auf Produktionsmaschinen) und so würde ich nicht darüber nachdenken.

Spielerisch mit Pufferung wird natürlich die Kommunikation sehr verlangsamen.

Dies implementiert die Idee von melpomene 's Kommentare.


& dolch; Mit "Puffer" beziehe ich mich auf den Puffer der Pipe (die Menge der vor den Pipes geschriebenen Daten, wenn auf der anderen Seite keine Daten gelesen werden). Das sind andere Puffer, die beteiligt sind, aber hier nicht so relevant sind.

+0

Ich kann noch keine Stimmen hinzufügen (noch nicht genug Privilegien), aber danke für diese großartige Antwort! – Hochstenbach

+0

@Hochstenbach Danke, dass es dir gefällt :). Lass es mich wissen, wenn es Fragen gibt. – zdim

Verwandte Themen