Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl AnyEvent callback on latency sub, how run it async?

I start to learn AnyEvent and have some trobles with it. I totally misunderstood how its possible to get asynchronous profit, fe :

#!/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

At output I got:

### 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'
###          }

All delay sum (1+1+2) eq $time_all - 4 seconds. So, no profit at all.

Why is it and how I can (and is it possible?) create "right" callback?

like image 706
Meettya Avatar asked Dec 27 '22 16:12

Meettya


2 Answers

Don't use condvars except to block your top-level program while waiting for events to complete. Using condvars makes it very difficult to reuse code; any function that has a condvar internally can never be safely used in a program that has another function that has a condvar in it. (This is not true if you never call recv and only use cb. But still... it's dangerous and not for those that don't know what they're doing.)

My rule: if the filename ends .pm, no condvars!

If you want to run multiple things in parallel and run some more code once all the results are available, try 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');

Then, after 3 seconds, you'll see "everything is done". Event::Join is like begin and end on condvars, but can never block your program. So it's easy to reuse code that uses it. Also, events are named, so you can collect results as a hash instead of just calling a callback when other callbacks are called.

like image 200
jrockway Avatar answered Jan 11 '23 11:01

jrockway


The call $cv->recv will block until ->send is called, so do_delay() takes $delay secs to return.

Here is an example of spawning three threads and waiting for all of them to complete:

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;
like image 31
perlman Avatar answered Jan 11 '23 11:01

perlman