Axum – Introduction and Ping Pong Example

As it was announced last week, Axum, a .NET Domain Specific Language around safe, scalable parallel programming through the actor model and message passing was released to the world as a CTP.  It was noted, that although this is an initial release, that this is still an incubation project which may or may not lead to a final product.  This post will serve as a kind of introduction to the language and some basic concepts.

Just A Brief Introduction

Before we get started, I wanted to make sure that we had a basic introduction to the language.  The question often comes up, why another .NET language?  Many people feel that with such others as C#, VB.NET, now F#, IronPython and IronRuby, that maybe we have enough languages already to solve most of our programming needs.  But there remains one important, yet untackled problem around making coordinated parallelization easy.  Sure, we have such things as the Parallel Extensions for .NET being baked into .NET 4.0, which gives us the Task Parallel Library, Parallel LINQ and so on, but none of these really address the intricate nature of coordinated parallelization.

Typically, when we’re developing these parallel applications, we could partition the data as usual and then scatter them out as parallel tasks.  Some jobs were meant for solutions such as this, but others might require a bit more coordination.  The Axum language, built upon the Microsoft Concurrency and Coordination Runtime from the Microsoft Robotics Studio, allows for a pretty natural language around such concepts as messaging, side effect free programming, but just as well, free yourself of many concurrency related bugs.  Because isolation is built-in as a default, one of the major sources of concurrency related bugs is taken away.  Just as well, when we design our applications with messaging in mind, we are building a scalable solution where concurrency isn’t an afterthought.

So, the final word is, why a language?  Built-in isolation, easy to understand syntax around messaging, easy asynchronous programming model and many other benefits we get by stepping outside of the C# bounds.  The real question then becomes, so what?  What can we do with it?

The Erlang Way

When learning language concepts, I find it easier to spend some time in the language which most strongly implements that concept.  This way, I learn the concepts best when they are first-class citizens in the space.  For learning functional programming, I don’t think it’s as wise to spend your time hacking on C# as to instead learn on a more functional language such as Haskell.  Often when people ask how best to learn FP concepts, often Haskell comes into the equation and has obviously improved my F# code as well as some of my C# as well.  The same applies to a language with actor model concurrency as a built-in concept, where I can learn from my time spent in Erlang as a good precursor to what I can do in Axum.

One of the really elementary things to do in Erlang is a simple ping-pong example.  In this simple example, we create two processes, a pinger and a ponger, and send messages between them a number of times.  Such code might look like the following:

-module(tut15).

-export([start/0, ping/2, pong/0]).

ping(0, Pong_PID) ->
    Pong_PID ! finished,
    io:format("ping finished~n", []);

ping(N, Pong_PID) ->
    Pong_PID ! {ping, self()},
    receive
        pong ->
            io:format("Ping received pong~n", [])
    end,
    ping(N - 1, Pong_PID).

pong() ->
    receive
        finished ->
            io:format("Pong finished~n", []);
        {ping, Ping_PID} ->
            io:format("Pong received ping~n", []),
            Ping_PID ! pong,
            pong()
    end.

start() ->
    Pong_PID = spawn(tut15, pong, []),
    spawn(tut15, ping, [3, Pong_PID]).

From this example, we have two functions, a ping and a pong.  Our ping has an arity with two arguments, the iteration number and the Process ID (PID) of the pong.  If we are at zero iterations left, then we sent the finished message back to the pong’s PID, else we send a ping to the pong, then receive our pong message and then recurse to decrement the iteration number.  Pretty simple, elegant and straight forward example.  Running this example, we might get the following:

Erlang R13B (erts-5.7.1) [smp:2:2] [rq:2] [async-threads:0]

Eshell V5.7.1  (abort with ^G)
1> c(tut15).
{ok,tut15}
2> tut15:start().
Pong received ping
<0.40.0>
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
ping finished
Pong finished

The <0.40.0> value you see above is the return value from the start() function which is nothing more than the PID of the ping process.  Now that we’ve seen this in action in Erlang, how could we do the same in Axum?

The Axum Solution

We have a couple of options when implementing this ping-pong example in Axum.  First, we have approach using channels, and another using an ordered interaction point.  Let’s walk through each of these solutions as a possible implementation.  The Axum Programming Guide is a great reference just in case you want to dive deeper into some of these topics.

Before we get started here, let’s first take a look at a simple hello world example to get you familiar with the syntax.  Below is a quick example of getting the command line arguments, and then setting the channel to say we are finished.  Alternatively, we could set the error code

using Microsoft.Axum;

public agent Program : channel Application
{
    public Program()
    {
        // Wait for the command line arguments
        var cmdLine = PrimaryChannel::CommandLine;
        var cmdArgs = receive(cmdLine);  
        
        // Tell the application we're done
        PrimaryChannel::Done <-- Signal.Value;
    }
}

The first piece you will notice is the lack of the word class, and instead the keyword agent.  The agent concept comes from the actor model of concurrency in which it is an autonomous entity that communicates with others via messages they receives from these other “actors” and can spawn off other “actors” as well.  These agents aren’t like normal classes in which you have fields that other classes can modify, nor have any direct method calls you can make. 

The second piece you’ll notice after the agent declaration is that our Program implements the Microsoft.Axum.Application Channel.  When we implement a channel, we are attached at the implementing end of that channel and we become the server of messages on that channel.  This channel has a public property called PrimaryChannel which gives access to the channel being implemented.  The double-colon operator gives us access to the channel’s port.

Now to actually dive through the agent constructor, we wait for the command line arguments to be passed.  This receive is a blocking call, so we will wait until we receive the data from the CommandLine port, which in this case will be a string array.  We then tell the application we are done by sending a message through the <—operator to the Done port, which is done asynchronously, as opposed to the receive.  Now that we looked at a basic example, let’s move on to a channel based approach.

The Channels Example

The first example we’re going to look at is using channels and protocols.  In this example, we will use some of the above constructs with the addition of channel protocols.  First, let’s look at the main code which starts the program:

using System;
using System.Concurrency;
using System.Collections.Generic;
using Microsoft.Axum;
using System.Concurrency.Messaging;

public agent Program : channel Application
{
    public Program()
    {
        // Wait for our command args
        var cmdArgs = receive(PrimaryChannel::CommandLine);
        
        // Set the iterations to be some number
        var iters = 3;
        
        // Create instance of ping and send msg
        var chan = Ping.CreateInNewDomain();
        chan::HowMany <-- iters;
        
        // Receive how many done
        receive(chan::Done);
        
        PrimaryChannel::Done <-- Signal.Value;
    }
}

This code first waits for our command arguments, then we set the iterations to 3 for now, but we could set it as high as we wish, maybe higher than 5000 if we care.  Sure, that’s a high number, but our system should be good for it.  We then call Ping.CreateInNewDomain() which instantiates two ends of the PingPongStatus channel, creates an instance of Ping that implements the channel, then returns the using end of the channel.  After this, we send a message on the HowMany port with our number of iterations desired.  Then, we’ll block until we’ve received our message on the Done port to indicate that we are finished.  Finally, we wrap up with setting the overall Done on the PrimaryChannel to indicate our program completion.  Let’s move onto the Ping implementation next.

public channel PingPongStatus
{
    input int HowMany;
    output int Done;
    
    Start: { HowMany, Done -> End; }
}

public agent Ping : channel PingPongStatus
{
    public Ping()
    {
        // How many are we supposed to do?
        var iters = receive(PrimaryChannel::HowMany);
        
        // Create pong and send how many to do
        var chan = Pong.CreateInNewDomain();      
        chan::HowMany <-- iters;
        
        // Send pings and receive pongs
        for (int i = 0; i < iters; i++)
        {
            chan::Ping <-- Signal.Value;
            receive(chan::Pong);
            Console.WriteLine("Ping received Pong");
        }
        
        // How many did Pong process?
        int pongIters = receive(chan::Done);

        PrimaryChannel::Done <-- 0;
    }  
}

First, we created a PingPongStatus channel to get an input of how many to process, and then we have an output message which says we’re complete.  You’ll notice there’s something more to our channel which begins with Start:.  This is an implementation of a protocol, which allows us to specify the order in which messages are sent to which ports.  All cases start with the Start as we have above, and then we transition to the HowMany port, then from the Done, it signifies the end of our interaction on this channel.

Now to analyze the Ping agent.  Quite simply, we receive the number of iterations message from our Program agent from above.  From there, we call Pong.CreateInNewDomain() which instantiates two ends of the PingPong channel, creates an instance of Pong that implements the channel, then returns the using end of the channel.  We send a message to the HowMany port to specify the number of iterations our ponger should do.  Going through each iteration, we send a message on the Ping port of a Signal, receive the message back on the Pong port.  Finally, we receive the number of iterations that our pong did and then set our Done flag to 0.  Now, let’s move onto our Pong implementation.

public channel PingPong
{
    input int HowMany;
    output int Done;
    
    input Signal Ping;
    output Signal Pong;
}

public agent Pong : channel PingPong
{
    public Pong()
    {
        // Get how many we're supposed to do
        var iters = receive(PrimaryChannel::HowMany);

        int i = 0;

        // Receive our ping and send back our pong
        for (; i < iters; i++)
        {
            receive(PrimaryChannel::Ping);
            Console.WriteLine("Pong received Ping");
            PrimaryChannel::Pong <-- Signal.Value;    
        }
    
        PrimaryChannel::Done <-- i;
    }
}

Notice that we have defined our PingPong channel to have ports to indicate how many to do, an output of how many we did, plus an input of a ping and an output of a pong.  Pretty straight forward and self explanatory.  Our Pong agent receives the number of iterations we’re supposed to do.  Then we loop through our iterations, receiving a ping and sending a pong.  After we finish the loop, we send a message on the Done port to indicate the number of iterations done. 

If we stick with our three iterations, the output from our application should look like the following:

Pong received Ping
Ping Received Pong
Pong received Ping
Ping Received Pong
Pong received Ping
Ping Received Pong

Pretty straight forward and easy example as we dip our toe into what we can do with Axum.  You can find the complete source code here.

Conclusion

There are other angles I wish to tackle with this example, including using asynchronous methods, ordered interaction points and so on, which I will get into in subsequent posts.  Until then, I hope this brief foray into some of the capabilities of Axum will inspire you to give Axum a look.  Download it, use it in anger, and give the team feedback on the MSDN forum.  With your help we can help shape the future of an actor based concurrency model on .NET.

1 Comment

Comments have been disabled for this content.