AnyEvent::Capture - Synchronous calls of async APIS
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
}
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 acapture
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;
@Joel— Huh, I’d never heard of that module (obviously). I’ll see if I can’t brainstorm a new name.
@Paul— Yes, exactly. Huh, IO::Async is interesting, do you have a page or a post somewhere explaining how you’d like it to fit into the wider Perl event loop/async programming module world? I’m having trouble figuring out what it’s place is – it seems to overlap a number of different projects, so I’m curious what your goals are with it.
> 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.
@schmorp: Er yes, the bit about libraries could be put more clearly... I had my mind primarily application-local libraries where, well, let's not say blocking per se, but stopping to wait for some of our outstanding event listeners to trigger is appropriate. There are times that I want to provide the simplicity of a synchronous interface without blocking unrelated event listeners.
Naming wise, it does actually use Sub::Exporter so you can rename it if you have conflicts. I need to mention that in my docs, however.
@joel: So yeah, if I can find another name that means what it says well enough, I'd prefer to avoid the cognitive clash. Among the thoughts I've had are: "sync" and "waitfor". I'm not super keen on "sync" because it's mostly wrong, if you trigger more then one event. I like "waitfor" a bit better, although it doesn't capture the idea that we're getting the result of the callback. So I'll have to think about this more... (and I'm open to suggestions).
Maybe “await_value”.
Oh, I like that, thank you Aristotle