Perl AnyEvent обратный вызов на sub задержки, как запустить его асинхронно? - PullRequest
3 голосов
/ 26 июля 2011

Я начинаю изучать AnyEvent и у меня возникают некоторые проблемы. Я совершенно не понял, как можно получить асинхронную прибыль, например:

#!/usr/bin/env perl

package LatencySub;

use strict;
use warnings;
use AnyEvent;

# sub for emulate latency - is it right way?
sub do_delay{
    my ($name, $delay) = (@_);
    my $cv = AE::cv;
    my $timer = AE::timer $delay, 0, sub { $cv->send() };
    $cv->recv;
    return $name.' proceed, delay is '.$delay;
};


package main;

use 5.12.0;
use warnings;

use Smart::Comments;

use AnyEvent;

my @list = (
    { name => 'first', delay => 1  },
    { name => 'second', delay => 1 },
    { name => 'third', delay => 2 }
);

sub process_cb {
    my ( $name, $delay, $cb ) = @_;
    my $result = LatencySub::do_delay( $name, $delay );
    $cb->($result);
}

my %result;

my $cv = AE::cv;
# outer loop
$cv->begin (sub { shift->send (\%result) });

my $before_time =  AE::time;
### foreach start...
foreach my $entity (@list) {
            $cv->begin;
            process_cb ( 
                                $entity->{'name'},
                                $entity->{'delay'},
                                sub {
                                     $result{$entity->{'name'}} = shift;
                                     $cv->end;
                                }
            );
     }
### foreach end...

$cv->end;
my $time_all = AE::time - $before_time;

### $time_all
### %result

На выходе я получил:

### foreach start...

### foreach end...

### $time_all: '4.02105116844177'
### %result: {
###            first => 'first proceed, delay is 1',
###            second => 'second proceed, delay is 1',
###            third => 'third proceed, delay is 2'
###          }

Вся сумма задержки (1 + 1 + 2) eq $ time_all - 4 секунды. Так что никакой прибыли вообще нет.

Почему и как я могу (и возможно ли?) Создать «правильный» обратный вызов?

Ответы [ 2 ]

5 голосов
/ 26 июля 2011

Вызов $cv->recv будет блокироваться до тех пор, пока не будет вызван ->send, поэтому do_delay() требуется $delay сек.завершено:

use strict;
use warnings;
use AnyEvent;

sub make_delay {
    my ($name, $delay, $cv) = (@_);
    $cv->begin;
    return AE::timer $delay, 0, sub { warn "done with $name\n"; $cv->end };
}

my $cv = AE::cv;

my @timers = (make_delay("t1", 3, $cv),
              make_delay("t2", 5, $cv),
              make_delay("t3", 4, $cv)
);

$cv->recv;
4 голосов
/ 27 июля 2011

Не используйте condvars, кроме как для блокировки программы верхнего уровня во время ожидания завершения событий. Использование condvars сильно затрудняет повторное использование кода; любая функция, которая имеет внутренний condvar, никогда не может безопасно использоваться в программе, в которой есть другая функция, в которой есть condvar. (Это неправда, если вы никогда не звоните recv и используете только cb. Но все же ... это опасно и не для тех, кто не знает, что делает.)

Мое правило: если имя файла оканчивается .pm, без условий!

Если вы хотите запустить несколько вещей параллельно и запустить еще один код, как только все результаты станут доступны, попробуйте Event :: Join :

sub delay($$) {
    AnyEvent->timer( after => $_[0], cb => $_[1] );
}

my $join = Event::Join->new(
    on_completion => sub { say "Everything is done" }
    events        => [qw/t1 t2 t3/],
);

delay 1, $join->event_sender_for('t1');
delay 2, $join->event_sender_for('t2');
delay 3, $join->event_sender_for('t3');

Затем, через 3 секунды, вы увидите «все сделано». Event :: Join похож на начало и конец на condvars, но никогда не может блокировать вашу программу. Так что легко использовать код, который его использует. Кроме того, события именуются, поэтому вы можете собирать результаты в виде хэша, а не просто вызывать обратный вызов при вызове других обратных вызовов.

...