LeoNerd.org.uk

IO-Async

What is it?

IO-Async is a collection of perl modules forming a framework for writing programs that wish to perform multiple operations concurrently, which are primarily blocking in nature. That is, the main bulk of the program's running time is spent waiting on IO events, timeouts, signals, or other external events, rather than spending its time in complex calculations.

Why use it?

Making tea

A naïve approach

Lets start with a good cup of tea.

sub make_tea
{
   my ( $who, $tea_type, $want_milk, $want_sugar ) = @_;

   $kettle->start_boil();

   sleep 1 while( !$kettle->boiled );

   my $mug = Mug->new();

   $mug->add( get_teabag( $tea_type ) );

   $mug->add( $kettle->pour() );

   $mug->add( get_milk ) if $want_milk;
   $mug->add( get_sugar ) for 1 .. $want_sugar;

   $who->give( $mug );
}

# Now lets take some tea orders
make_tea( 'Bob', 'earl grey', 0, 1 );
make_tea( 'Jim', 'English breakfast', 1, 0 );
make_tea( 'Jane', 'PG', 1, 2 );

Not a very good algorithm. What if fetching the teabags, or the milk, or the sugar, takes us a while? We'll let the tea cool down in the meantime. Perhaps we can find something better.

Overlapping

As a first approach, we could notice that it takes a while for the kettle to boil. While we're waiting for it, lets get the other bits together.

sub make_tea
{
   my ( $who, $tea_type, $want_milk, $want_sugar ) = @_;

   $kettle->start_boil();

   my $mug = Mug->new();

   my $teabag = get_teabag( $tea_type );
   my $milk = $want_milk ? get_milk : undef;
   my @sugar = map { get_sugar } 1 .. $want_sugar;

   sleep 1 while( !$kettle->boiled );

   $mug->add( $teabag );
   $mug->add( $kettle->pour );
   $mug->add( $milk ) if $milk;
   $mug->add( $_ ) for @sugar;

   $who->give( $mug );
}

This is a little better, but we can go further. This boring sleep 1 loop is not going to make us very efficient at making tea, as we can only make tea for one person at once. What we need is a way to make lots of tea at the same time.

Continuations

For this, we can alter the interfaces of anything likely to take a while. Rather than calling a function which takes a while to perform its work, then eventually returns, we can instead make sure the function doesn't block, and return immediately so we can do something else. But then how are we going to get the result back?

What we can do is pass in a continuation. This is a code reference containing more code to execute when the thing we were waiting for finally happens.

To do this under IO-Async, everything waits on the Loop object. No piece of code should ever block, except for the main loop. Using continuations allows functions to perform their initial work, then wait for whatever it is to happen, then react to it later. Inbetween times, other things can happen.

TODO: Find a way to explain eventually arriving at:

use IO::Async::Loop;

sub make_tea
{
   my %args = @_;

   my $mp = IO::Async::MergePoint->new( needs => [qw( water teabag )] );

   Kettle->boil( on_boiled => sub { $mp->done( water => shift ); } );

   my $teabag;
   get_teabag( $args{tea}, on_teabag => sub {
      $mp->done( 'teabag' );
      $teabag = shift;
   } );

   if( $args{want_milk} ) {
      $mp->needs( 'milk' );
      get_milk( on_milk => sub { $mp->done( milk => shift ) }; );
   }

   if( my $how_many = $args{want_sugar} ) {
      $mp->needs( 'sugar' );
      get_sugar( $how_many, on_sugar => sub { $mp->done( sugar => [ @_ ] ); } );
   }

   $mp->close( on_finished => sub {
      my %parts = @_;

      my $mug = Mug->new();

      $mug->add( $pa
   } );
}

How

Links