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
}

8 Comments

I cannot comment on the utility of this interface, because I don’t use asynchronous programming; your module certainly looks useful for simplifying code. My concern is that your function name capture and worse that it takes a block, is going to be cognitive dissonance for those of use who are accustomed to Capture::Tiny which also provides a capture function which takes a block.

This is probably not a huge concern, but I would see capture { something } and think that I know what is going on and be completely wrong.

It's basically the same as IO::Async's await method, then.

my @ips = $loop->await( $getaddrinfo_task )->get;

> specially in libraries, where your code should block

*Especially* in libraries you should _never_ block, because that means only one such library can work concurrently, which in turn defeats the whole purpose of using events.

(I interpreted "library" to mean perl modules)

Coro can work around this, but a reasonably-designed event-using module that doesn't rely on Coro must be event-based, and thus cannot use your capture, because that makes it instantly non-event-based.

@joel: lots of modules use capture {}, which is why perl has namespaces. capture *is* an appropriate name, and renaming it to something less appropriate would decrease the quality of the module, with no real benefit (you can write AnyEvent::Capture::capture to make it clear for example, or rename it to something you like more).

np. I was just pointing out a fact that if I were reading the code, that is what I would see. Rebecca mentioned that she had been unaware of the potential for cognitive clashing, so it wasn't in vain. Its now fully up to her if she would like to keep the name or not. Of course you can fully qualify a name, this is why we only @EXPORT when needed.

Maybe “await_value”.

Leave a comment

About Rebecca

user-pic I've been happily playing in Perl's playground since 1997, when I decided I really wanted to work with Unix and the Web, rather then DOS, where my previous work had been. Lately, my rate of publishing has code has increased a lot, in large part thanks to Github.