NAME
Async::Selector - level-triggered resource observer like select(2)
VERSION
1.03
SYNOPSIS
use Async::Selector;
my $selector = Async::Selector->new();
## Register resource
my $resource = "some text."; ## 10 bytes
$selector->register(resource_A => sub {
## If length of $resource is more than or equal to $threshold bytes, provide it.
my $threshold = shift;
return length($resource) >= $threshold ? $resource : undef;
});
## Watch the resource with a callback.
$selector->watch(
resource_A => 20, ## When the resource gets more than or equal to 20 bytes...
sub { ## ... execute this callback.
my ($watcher, %resource) = @_;
print "$resource{resource_A}\n";
$watcher->cancel();
}
);
## Append data to the resource
$resource .= "data"; ## 14 bytes
$selector->trigger('resource_A'); ## Nothing happens
$resource .= "more data"; ## 23 bytes
$selector->trigger('resource_A'); ## The callback prints 'some text.datamore data'
DESCRIPTION
Async::Selector is an object that watches registered resources and executes callbacks when some of the resources are available. Thus it is an implementation of the Observer pattern like Event::Notify, but the important difference is that Async::Selector is level-triggered like select(2)
system call.
Basic usage of Async::Selector is as follows:
Register as many resources as you like by
register()
method.A resource has its name and resource provider. A resource provier is a subroutine reference that returns some data (or
undef
if it's not available).Watch as many resources as you like by
watch()
method.When any of the watched resources gets available, a callback function is executed with the available resource data.
Note that if some of the watched resources is already available when calling
watch()
method, it executes the callback function immediately. That's because Async::Selector is level-triggered.Notify the Async::Selector object by
trigger()
method that some of the registered resources have changed.The Async::Selector object then checks if any of the triggered resources gets available. If some resources become available, the callback function given by
watch()
method is executed.
CLASS METHODS
$selector = Async::Selector->new();
Creates an Async::Selector object. It takes no parameters.
OBJECT METHODS
$selector->register($name => $provider->($condition_input), ...);
Registers resources with the object. A resource is described as a pair of resource name and resource provider. You can register as many resources as you like.
The resource name ($name
) is an arbitrary string. It is used to select the resource in watch()
method. If $name
is already registered with $selector
, the resource provider is updated with $provider
and the old one is discarded.
The resource provider ($provider
) is a subroutine reference. Its return value is supposed to be a scalar data of the resource if it's available, or undef
if it's NOT available.
$provider
subroutine takes a scalar argument ($condition_input
), which is given by the user in arguments of watch()
method. $provider
can decide whether to provide the resource according to $condition_input
.
register()
method returns $selector
object itself.
$selector->unregister($name, ...);
Unregister resources from $selector
object.
$name
is the name of the resource you want to unregister. You can unregister as many resources as you like.
unregister()
returns $selector
object itself.
$watcher = $selector->watch($name => $condition_input, ..., $callback->($watcher, %resources));
Starts to watch resources. A watch is described as pairs of resource names and condition inputs for the resources.
$name
is the resource name that you want to watch. It is the name given in register()
method.
$condition_input
describes the condition the resource has to meet to be considered as "available". $condition_input
is an arbitrary scalar, and its interpretation is up to the resource provider.
You can list as many $name => condition_input
pairs as you like.
$callback
is a subroutine reference that is executed when any of the watched resources gets available. Its first argument $watcher
is an object of Async::Selector::Watcher which represents the watch you just made by watch()
method. This object is the same instance as the return value of watch()
method. The other argument (%resources
) is a hash whose keys are the available resource names and values are the corresponding resource data. Note that $callback
is executed before watch()
method returns if some of the watched resources is already available.
The return value of $callback
is just ignored by Async::Selector.
watch()
method returns an object of Async::Selector::Watcher ($watcher
) which represents the watch you just made by watch()
method. $watcher
gives you various information such as the list of watched resources and whether the watcher is active or not. See Async::Selector::Watcher for detail.
The watcher created by watch()
method is persistent in nature, i.e., it remains in the Async::Selector object and $callback
can be executed repeatedly. To cancel the watcher and release the $callback
, call $watcher->cancel()
method.
If no resource selection ($name => $condition_input
pair) is specified, watch()
method silently ignores it. As a result, it returns a $watcher
object which is already canceled and inactive.
$watcher = $selector->watch_lt(...);
watch_lt()
method is an alias for watch()
method.
$watcher = $selector->watch_et(...);
This method is just like watch()
method but it emulates edge-triggered watch.
To emulate edge-triggered behavior, watch_et()
won't execute the $callback
immediately even if some of the watched resources are available. The $callback
is executed only when trigger()
method is called on resources that are watched and available.
$selector->trigger($name, ...);
Notify $selector
that the resources specified by $name
s may be changed.
$name
is the name of the resource that might have been changed. You can specify as many $name
s as you like.
Note that you may call trigger()
on resources that are not actually changed. It is up to the resource provider to decide whether to provide the resource to watchers.
trigger()
method returns $selector
object itself.
@resouce_names = $selector->resources();
Returns the list of registered resource names.
$is_registered = $selector->registered($resource_name);
Returns true if $resource_name
is registered with the Async::Selector object. Returns false otherwise.
@watchers = $selector->watchers([@resource_names]);
Returns the list of active watchers (Async::Selector::Watcher objects) from the Async::Selector object.
If watchers()
method is called without argument, it returns all of the active watchers.
If watchers()
method is called with some arguments (@resource_names
), it returns active watchers that watch ANY resource out of @resource_names
.
If you want watchers that watch ALL of @resource_names
, try filtering the result (@watchers
) with Async::Selector::Watcher's resources()
method.
EXAMPLES
Level-triggered vs. edge-triggered
Watchers created by watch()
and watch_lt()
methods are level-triggered. This means their callbacks can be immediately executed if some of the watched resources are already available.
Watchers created by watch_et()
method are edge-triggered. This means their callbacks are never executed at the moment watch_et()
is called.
Both level-triggered and edge-triggered watcher callbacks are executed when some of the watched resources are trigger()
-ed AND available.
my $selector = Async::Selector->new();
my $a = 10;
$selector->register(a => sub { my $t = shift; return $a >= $t ? $a : undef });
## Level-triggered watch
$selector->watch_lt(a => 5, sub { ## => LT: 10
my ($watcher, %res) = @_;
print "LT: $res{a}\n";
});
$selector->trigger('a'); ## => LT: 10
$a = 12;
$selector->trigger('a'); ## => LT: 12
$a = 3;
$selector->trigger('a'); ## Nothing happens because $a == 3 < 5.
## Edge-triggered watch
$selector->watch_et(a => 2, sub { ## Nothing happens because it's edge-triggered
my ($watcher, %res) = @_;
print "ET: $res{a}\n";
});
$selector->trigger('a'); ## => ET: 3
$a = 0;
$selector->trigger('a'); ## Nothing happens.
$a = 10;
$selector->trigger('a'); ## => LT: 10
## => ET: 10
Multiple resources, multiple watches
You can register multiple resources with a single Async::Selector object. You can watch multiple resources with a single call of watch()
method. If you watch multiple resources, the callback is executed when any of the watched resources is available.
my $selector = Async::Selector->new();
my $a = 5;
my $b = 6;
my $c = 7;
$selector->register(
a => sub { my $t = shift; return $a >= $t ? $a : undef },
b => sub { my $t = shift; return $b >= $t ? $b : undef },
c => sub { my $t = shift; return $c >= $t ? $c : undef },
);
$selector->watch(a => 10, sub {
my ($watcher, %res) = @_;
print "Select 1: a is $res{a}\n";
$watcher->cancel();
});
$selector->watch(
a => 12, b => 15, c => 15,
sub {
my ($watcher, %res) = @_;
foreach my $key (sort keys %res) {
print "Select 2: $key is $res{$key}\n";
}
$watcher->cancel();
}
);
($a, $b, $c) = (11, 14, 14);
$selector->trigger(qw(a b c)); ## -> Select 1: a is 11
print "---------\n";
($a, $b, $c) = (12, 14, 20);
$selector->trigger(qw(a b c)); ## -> Select 2: a is 12
## -> Select 2: c is 20
One-shot and persistent watches
The watchers are persistent by default, that is, they remain in the Async::Selector object no matter how many times their callbacks are executed.
If you want to execute your callback just one time, call $watcher->cancel()
in the callback.
my $selector = Async::Selector->new();
my $A = "";
my $B = "";
$selector->register(
A => sub { my $in = shift; return length($A) >= $in ? $A : undef },
B => sub { my $in = shift; return length($B) >= $in ? $B : undef },
);
my $watcher_a = $selector->watch(A => 5, sub {
my ($watcher, %res) = @_;
print "A: $res{A}\n";
$watcher->cancel(); ## one-shot callback
});
my $watcher_b = $selector->watch(B => 5, sub {
my ($watcher, %res) = @_;
print "B: $res{B}\n";
## persistent callback
});
## Trigger the resources.
## Execution order of watcher callbacks is not guaranteed.
($A, $B) = ('aaaaa', 'bbbbb');
$selector->trigger('A', 'B'); ## -> A: aaaaa
## -> B: bbbbb
print "--------\n";
## $watcher_a is already canceled.
($A, $B) = ('AAAAA', 'BBBBB');
$selector->trigger('A', 'B'); ## -> B: BBBBB
print "--------\n";
$B = "CCCCCCC";
$selector->trigger('A', 'B'); ## -> B: CCCCCCC
print "--------\n";
$watcher_b->cancel();
$selector->trigger('A', 'B'); ## Nothing happens.
Watcher aggregator
Sometimes you might want to use multiple Async::Selector objects and watch their resources simultaneously. In this case, you can use Async::Selector::Aggregator to aggregate watchers produced by Async::Selector objects. See Async::Selector::Aggregator for details.
my $selector_a = Async::Selector->new();
my $selector_b = Async::Selector->new();
my $A = "";
my $B = "";
$selector_a->register(resource => sub { my $in = shift; return length($A) >= $in ? $A : undef });
$selector_b->register(resource => sub { my $in = shift; return length($B) >= $in ? $B : undef });
my $watcher_a = $selector_a->watch(resource => 5, sub {
my ($watcher, %res) = @_;
print "A: $res{resource}\n";
});
my $watcher_b = $selector_b->watch(resource => 5, sub {
my ($watcher, %res) = @_;
print "B: $res{resource}\n";
});
## Aggregates the two watchers into $aggregator
my $aggregator = Async::Selector::Aggregator->new();
$aggregator->add($watcher_a);
$aggregator->add($watcher_b);
## This cancels both $watcher_a and $watcher_b
$aggregator->cancel();
print("watcher_a: " . ($watcher_a->active ? "active" : "inactive") . "\n"); ## -> watcher_a: inactive
print("watcher_b: " . ($watcher_b->active ? "active" : "inactive") . "\n"); ## -> watcher_b: inactive
Real-time Web: Comet (long-polling) and WebSocket
Async::Selector can be used for foundation of so-called real-time Web. Resource registered with an Async::Selector object can be pushed to Web browsers via Comet (long-polling) and/or WebSocket.
See Async::Selector::Example::Mojo for detail.
COMPATIBILITY
The following methods that existed in Async::Selector v0.02 or older are supported but not recommended in this version.
select()
select_lt()
select_et()
selections()
cancel()
Currently the watch
methods are substituted for the select
methods.
The differences between watch
and select
methods are as follows.
watch
methods take the watcher callback from the last argument, whileselect
methods take it from the first argument.watch
methods return Async::Selector::Watcher objects, whileselect
methods return selection IDs, which are strings.The callback function for
watch
receives Async::Selector::Watcher object from the first argument, while the callback forselect
receives the selection ID.The second argument for the callback function is also different. For
watch
methods, it is a hash of resources that are watched, triggered and available. Forselect
methods, it is a hash of all the watched resources with values for unavailable resources beingundef
.Return values from the callback function for
watch
methods are ignored, while those forselect
methods are used to automatically cancel the selection.trigger()
method executes the callback forwatch
methods when it triggers resources that are watched and available. On the other hand,trigger()
method executes the callback forselect
when it triggers resources that are watched, and some of the watched resources are available. So if you trigger an unavailable watched resource and don't trigger any available watched resource, theselect
callback is executed with available resources even though they are not triggered.
SEE ALSO
Event::Notify, Notification::Center
AUTHOR
Toshio Ito, <toshioito at cpan.org>
BUGS
Please report any bugs or feature requests to bug-async-selector at rt.cpan.org
, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Async-Selector. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Async::Selector
You can also look for information at:
RT: CPAN's request tracker (report bugs here)
AnnoCPAN: Annotated CPAN documentation
CPAN Ratings
Search CPAN
LICENSE AND COPYRIGHT
Copyright 2012-2013 Toshio Ito.
This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.