Async iterators
Marc Lehmann
schmorp at schmorp.de
Mon Aug 6 18:25:55 CEST 2012
On Mon, Aug 06, 2012 at 03:03:49PM +0200, Clinton Gormley <clint at traveljury.com> wrote:
> I'm a bit clueless about event-driven programming, but I've had requests
You should look at AnyEvent::Intro, or maybe google some intros about
event-driven programming in general then.
> next() checks if there are sufficient docs in the buffer to satisfy the
> request and if not, calls refill_buffer() and loops to check again.
next() is fundamentally not event based.
> My first attempt at converting this module looks like this:
>
> https://gist.github.com/3274257
return if in_global_destruction;
Looks painful :)
> I know that I shouldn't be calling AnyEvent->_poll() but I don't know
> how I should be approaching this.
Calling internal methods will not even do it if it were supported: your API
is fundamnetally not event-driven.
> One suggestion that was made to me was to expose something like:
>
> $cv = $s->ensure_buffer_is_ready;
>
> which would allow the user to decide how to block, poll whatever, but
> this feels like I'm leaking implementation details.
The only way to make a blocking API event driven is to change it. For
example, like this:
$s->next (100, sub { my ($s, @results) = ... });
> It may be that the interface to an event-driven iterator should be
> completely different. I assume that there are well known patterns for
> doing this kind of thing in the async world, but I've not had much
> success in finding them.
Well, it's pretty easy: "don't call us, we call you", that is, instead of:
my @results = some_function ...;
The API needs to be:
some_function ..., sub { # called with results
Now, there are a few ways on how to make your module anyevent-aware:
1) get rid of internal calls such as _poll (which will likely break sooner
or later) and use condvars. that means your module is not event driven,
and only one such module can do things concurrently. if you documented
this properly then users could employ Coro and just run your module
inside it's own thread - you can block on condvars from multiple
threads.
that means no event-driven usage, no concurrency of your module with
others, but at least it wouldn't block out event-driven things, and could
be worked around with Coro, without your module having a dependence on
Coro, e.g. users could just do:
# unfortunately, $search is not event-driven, but it can coexist with
# AnyEvent, so we use Coro to work arpund that:
async {
my @result = $search->next (100);
};
it's the worst possible solution.
2) change your api to be event-driven, by always returning "instantly" and
instead calling a callback with results.
3) create a new api as in #2, to be used in parallel. the "old" api could
even use the anyevent apiand block by using condvars, e.g.:
sub next {
my ($self, $count, $cb) = @_;
if ($cb) {
# event based
} else {
# block
$self->next ($count, my $cv = AE::cv);
return $cv->recv;
}
}:
Basically, if you want your module to coexist with others in the same
process concurrently, then you must not block, not with, nor without
anyevent. If you want to rely on your users having to use Coro to use
concurrency, then using condvars and blocking on them would do the job. If
you want to be event-driven, your API must be based on callbacks and never
wait on I/O or other events itself.
--
The choice of a Deliantra, the free code+content MORPG
-----==- _GNU_ http://www.deliantra.net
----==-- _ generation
---==---(_)__ __ ____ __ Marc Lehmann
--==---/ / _ \/ // /\ \/ / schmorp at schmorp.de
-=====/_/_//_/\_,_/ /_/\_\
More information about the anyevent
mailing list