Promises

The simplest way to make a Promise is using the start {} block.

start { sleep 1; say 'Two'; sleep 2; say 'Four' }
say 'One';
sleep 2;
say 'Three';

Magically, this will print out One Two Three so we can tell the code in the start block is executed concurrently with the code below it. We don't see Four printed out though. Why? We reached the end of the program with the line say 'Three' and the program ends. In reality start { ... } is the same thing as doing Promise.start( { ... } ) so it returns a Promise object. Promise's made by start will be fullfilled if the code inside the start block terminates without an exception. We don't get Four printed out because our code doesn't wait for the Promise to be fullfilled. How now to wait for the start block to complete? Enter await. We call await with the Promise returned by the start block, and use it like so:

my $promise = start { sleep 1; say 'Two'; sleep 2; say 'Four' }
say 'One';
sleep 2;
say 'Three';
await $promise;

This gives us the output we want: One Two Three Four

Here we find all markdown extension files and pass them through to the Unix/Linux file command and check if they are all either UTF-8 or ASCII and also make sure none have the BOM (Byte Order Marker):

my @files = qx{find . -name '*.markdown'}.lines;
for @files -> $file {
    my $file_type = qqx{file $file};
    say "$file is not UTF-8" if $file_type !~~ / 'UTF-8' | ASCII /;
    say "$file has BOM" if $file_type ~~ / BOM /;
}

Making this program asynchronous and multithreaded is only three words away! Perl 6 will spawn as many processes as it thinks will complete the job as quickly as possible. In my case it used 10 threads.

my @files = qx{find . -name '*.markdown'}.lines;
await do for @files -> $file {
    start {
        my $file_type = qqx{file $file};
        say "$file is not UTF-8" if $file_type !~~ / 'UTF-8' | ASCII /;
        say "$file has BOM" if $file_type ~~ / BOM /;
    }
}

You have seen how Perl 6 makes it easy to make multithreaded programs. Now lets look at using channels to communicate between parts of the program.

Channels

Channels are asynchronous queues. We create a Channel with Channel.new. Using the .closed method on a channel will return a Promise that will be kept when the channel is both closed and the queue is empty. We send data on a Channel by calling the .send method on it. We can close a Channel with the .close method. Know that $chan.closed will only evaluate as true if we have removed all the items from the queue and we have closed it with .closed.

my $chan = Channel.new;
$is_closed = $chan.closed;
$is_closed.then( { note "It's closed!" } );
for qx{ find . -name '*.markdown' }.lines -> $line {
    $chan.send($line)
}

We can call the .then method on a promise to schedule the code inside the block to be executed when the Promise is kept. In this case when the Channel is closed (.then actually returns another promise, but you don't have to use it if you don't want to).

We can poll a Channel with the .poll method which returns the next item in the channel in a nonblocking manner, but this is not very efficient:

my @promises;
while ! $is_closed  {
    if $chan.poll -> $file {
        push @promises, start {
            my $file_type = qqx{file "$file"};
            note "'$file' is not UTF-8" if $file_type !~~ / 'UTF-8' | ASCII /;
            note "'$file' has BOM" if $file_type ~~ / BOM /;
        }
    }
}
await Promise.allof(@promises);

Above we create an array called @promises and add to it each Promise returned by start. Then we await for all of the Promises of the worker threads to be kept. The .allof method returns a Promise that's only kept when all of the Promises in @promises have been kept.

React, Don't Poll

Using a react block we can create a tap on a Channel or Supply using the whenever keyword. (More on taps and Supplies later).

my @promises;
react {
    whenever $chan -> $file {
        push @promises, start {
            my $file_type = qqx{file "$file"};
            note "'$file' is not UTF-8" if $file_type !~~ / 'UTF-8' | ASCII /;
            note "'$file' has BOM" if $file_type ~~ / BOM /;
            say "'$file'";
        }
    }
}
await Promise.allof(@promises);

This has the advantage of not looping unnecessarily. If we wanted to react while code below was executing we could wrap the react in a start block (though in this case we don't need to do this).

Make sure to check out Part 2 where we will discuss Supplies and data concurrency!