August 2012 Archives

AnyEvent::Capture - Synchronous calls of async APIS

Cage7.jpg I’ve been a busy little bee lately, and have published a handful of new CPAN modules— I’ll be posting about all of them, but to start things off, I bring you: AnyEvent::Capture

It adds a little command to make calling async APIs in a synchronous, but non-blocking manner easy. Let’s start with an example of how you might do this without my shiny new module:

 use AnyEvent::Socket qw( inet_aton );

 my $cv = AE::cv;
 inet_aton( 'localhost', sub { $cv->send(@_) });
 my @ips = $cv->recv;
 say join ".", unpack("C*") for @ips;

The above is not an uncommon pattern when using AnyEvent, especially in libraries, where your code should block, but you don’t want to block other event listeners. AnyEvent::Capture makes this pattern a lot cleaner:

use AnyEvent::Capture;
use AnyEvent::Socket qw( inet_aton );

my @ips = capture { inet_aton( 'localhost', shift ) };
say join ".", unpack("C*") for @ips;

The AnyEvent::DBus documentation provides another excellent example of just how awkward this can be:

use AnyEvent;
use AnyEvent::DBus;
use Net::DBus::Annotation qw(:call);

my $conn = Net::DBus->find; # always blocks :/
my $bus  = $conn->get_bus_object;

my $quit = AE::cv;

$bus->ListNames (dbus_call_async)->set_notify (sub {
   for my $name (@{ $_[0]->get_result }) {
      print "  $name\n";
   }
   $quit->send;
});

$quit->recv;

With AnyEvent::Capture this would be:

use AnyEvent;
use AnyEvent::Capture;
use AnyEvent::DBus;
use Net::DBus::Annotation qw(:call);

my $conn = Net::DBus->find; # always blocks :/
my $bus  = $conn->get_bus_object;

my $reply = capture { $bus->ListNames(dbus_call_async)->set_notify(shift) };
for my $name (@{ $reply->get_result }) {
   print "  $name\n";
}

We can also find similar examples in the Coro documentation, where rouse_cb/rouse_wait replace condvars:

sub wait_for_child($) {
    my ($pid) = @_;

    my $watcher = AnyEvent->child (pid => $pid, cb => Coro::rouse_cb);

    my ($rpid, $rstatus) = Coro::rouse_wait;
    $rstatus
}

Even still, for the common case, AnyEvent::Capture provides a much cleaner interface, especially as it will manage the guard object for you.

sub wait_for_child($) {
    my ($pid) = @_;

    my($rpid, $rstatus) = capture { AnyEvent->child (pid => $pid, cb => shift) };

    $rstatus
}

About Rebecca

user-pic I blog about Perl.