NAME
EV::Future - Minimalist high-performance async control flow for EV
SYNOPSIS
use EV;
use EV::Future;
my @w;
parallel([
sub { my $done = shift; push @w, EV::timer 0.1, 0, sub { $done->() } },
sub { my $done = shift; push @w, EV::timer 0.2, 0, sub { $done->() } },
], sub { print "all done\n" });
parallel_limit([
sub { my $done = shift; push @w, EV::timer 0.1, 0, sub { $done->() } },
sub { my $done = shift; push @w, EV::timer 0.2, 0, sub { $done->() } },
sub { my $done = shift; push @w, EV::timer 0.1, 0, sub { $done->() } },
], 2, sub { print "all done (max 2 in-flight)\n" });
series([
sub { my $done = shift; $done->() },
sub { my $done = shift; $done->() },
], sub { print "all done in order\n" });
race([
sub { my $done = shift; push @w, EV::timer 0.1, 0, sub { $done->("a") } },
sub { my $done = shift; push @w, EV::timer 0.2, 0, sub { $done->("b") } },
], sub { my $winner = shift; print "winner: $winner\n" });
EV::run;
DESCRIPTION
Four control-flow primitives (parallel, parallel_limit, series, race), implemented in XS for minimal overhead. All four are exported by default.
Each task is a coderef that receives a single done callback as its only argument; the task must invoke done exactly once to mark completion.
If \@tasks is empty, final_cb fires immediately. Non-coderef elements in \@tasks are treated as no-op tasks that complete instantly.
Safe vs unsafe mode
Each function takes an optional trailing $unsafe flag. In safe mode (the default), each dispatch is wrapped in G_EVAL, every task gets its own done CV, and double-calls are silently dropped. Unsafe mode skips G_EVAL and reuses a single shared CV, roughly doubling throughput at the cost of:
Exceptions from a task bypass cleanup and leak the internal context.
Double-calling
donecorrupts the completion counter, which may invokefinal_cbbefore all tasks have actually finished.
Use unsafe mode only when tasks are well-behaved and performance is critical.
FUNCTIONS
parallel(\@tasks, \&final_cb, [$unsafe])
Dispatch every task immediately; call final_cb once each task has invoked its done callback.
parallel_limit(\@tasks, $limit, \&final_cb, [$unsafe])
Dispatch tasks with at most $limit in flight at any time. $limit is clamped to 1..scalar(@tasks): $limit == 1 degenerates to series, $limit >= @tasks degenerates to parallel.
There is no cancellation mechanism; all dispatched tasks must complete. The truthy-done cancellation supported by series does not apply here.
series(\@tasks, \&final_cb, [$unsafe])
Run tasks sequentially; each task starts only after the previous calls its done. To cancel the series and skip remaining tasks, pass a true value to done:
series([
sub { my $d = shift; $d->(1) }, # cancel here
sub { die "never reached" },
], sub { print "finished early\n" });
Cancellation works in both safe and unsafe modes.
race(\@tasks, \&final_cb, [$unsafe])
Dispatch every task; call final_cb with the arguments passed to the first done invocation. Subsequent done calls (whether from the winning task or losers) are silently ignored.
Losing tasks continue to run; EV::Future does not cancel their EV watchers (it didn't create them). To tear losers down, hold their watchers in a shared lvalue and clear it from final_cb:
my @w;
race([
sub { my $d = shift; push @w, EV::timer 0.1, 0, sub { $d->("a") } },
sub { my $d = shift; push @w, EV::timer 0.2, 0, sub { $d->("b") } },
], sub { my $winner = shift; @w = () });
Non-coderef elements in \@tasks count as instantly-completed winners (with no arguments) and short-circuit dispatch.
BENCHMARKS
1000 synchronous tasks, 5000 iterations (bench/comparison.pl):
--- PARALLEL (iterations/sec) ---
EV::Future (unsafe) 4,386
EV::Future (safe) 2,262
AnyEvent::cv (begin/end) 1,027
Future::XS::wait_all 982
Promise::XS::all 32
--- PARALLEL LIMIT 10 (iterations/sec) ---
EV::Future (unsafe) 4,673
EV::Future (safe) 2,688
Future::Utils::fmap_void 431
--- SERIES (iterations/sec) ---
EV::Future (unsafe) 5,000
AnyEvent::cv (stack-safe) 3,185
EV::Future (safe) 2,591
Future::XS (chain) 893
Promise::XS (chain) 809
SEE ALSO
AUTHOR
vividsnow
LICENSE
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.