The Polyglot Programmer

Concurrency with Channels, Domains and Messages

Ted Neward

In the last article, we looked at the programming language Cobra, an object-oriented language loosely derived from Python that embraces both static- and dynamic-typing, some "scripting" concepts similar to those found in Python and Ruby, and baked-in unit-testing features, among other things. Upon deeper investigation, we saw that it had value and we were happy to have learned a new general-purpose programming language.

As interesting and powerful as general-purpose languages are, however, sometimes what's needed is not a hammer, saw or screwdriver, but a 3/16-inch nut driver; in other words, as much as we as developers value the tools that provide wide-ranging capability and facility, sometimes the right tool for the job is a very, very specific one. In this piece, we'll focus not on a language that re-creates all of the Turing-complete constructs that we're used to, but a language that focuses on one particular area, seeking to do it well, in this case, concurrency.

Careful and thorough readers of this magazine will note that this concurrency meme is not an unfamiliar one to these pages. The past few years have seen numerous articles on concurrency come through here, and MSDN Magazine is hardly unique in that respect -- the blogosphere, Twitterverse and developer Web portals are all rife with discussions of concurrency, including a few naysayers who think this whole concurrency thing is another attempt to create a problem out of thin air. Some industry experts have even gone so far as to say (according to rumor, at a panel discussion at an OOPSLA conference a few years ago) that "concurrency is the dragon we must slay in the coming decade."

Some developers will be wondering what the problem really is -- after all, .NET has had multi-threading capability for years, via the asynchronous delegate invocation idiom (BeginInvoke/EndInvoke), and provides thread-safety mechanisms via the monitor construct (the lock keyword from C#, for example) and other explicit concurrency object constructs (Mutex, Event, and so on, from System.Threading). So all this new fuss seems like making a mountain out of a molehill. Those developers would be absolutely right, assuming they write code that is (1) 100 percent thread-safe, and (2) taking advantage of every opportunity for task or data parallelism in their code by spinning up threads or borrowing threads from thread pools to maximize the use of all the cores provided by the underlying hardware. (If you're safe on both counts, please move on to the next article in the magazine. Better yet, write one and tell us your secrets.)

Numerous proposals to solve, or at least mitigate, this problem have been floated through the Microsoft universe, including the Task Parallel Library (TPL), Parallel FX extensions (PFX), F# asynchronous workflows, the Concurrency and Control Runtime (CCR), and so on. In each of these cases, the solution is either to extend a host language (usually C#) in ways to provide additional parallelism or to provide a library that can be called from a .NET general-purpose language. The drawback to most of these approaches, however, is that because they depend on the semantics of the host language, doing the right thing, from a concurrency perspective, is an elective choice, something developers have to explicitly buy into and code appropriately. This unfortunately means that the opportunity for doing the wrong thing, whatever that may be, thus creates a concurrency hole that code can ultimately fall into and create a bug waiting to be discovered, and this is a bad thing. Developer tools shouldn't, in the ideal, allow developers to do the wrong thing -- this is why the .NET platform moved to a garbage-collected approach, for example. Instead, developer tools should cause developers to, as Rico Mariani (Microsoft Visual Studio architect and former CLR Performance architect) puts it, "fall into the pit of success" -- what developers find the easiest to do should be the right thing, and what developers find hard to do or find impossible to do should be the wrong thing.

Toward that end, Microsoft has released as an incubation project a new language aimed squarely at the concurrency domain, called "Axum." In the spirit of enabling developers to "fall into the pit of success," Axum is not a general-purpose language like C# or Visual Basic, but one aimed squarely at the problem of concurrency, designed from the outset to be part of a suite of languages that collectively cooperate to solve a business problem. In essence, Axum is a great example of a domain-specific language, a language designed specifically to solve just one vertical aspect of a problem.

As of this writing, Axum is labeled as version 0.2, and the details of Axum's language and operation are entirely subject to change, as stated by the disclaimer at the front of the article. But between "as I write this" and "as you read this," the core concepts should remain fairly stable. In fact, these concepts are not unique to Axum and are found in a number of other languages and libraries (including several of the aforementioned projects, such as F# and the CCR), so even if Axum itself doesn't make it into widespread use, the ideas here are invaluable for thinking about concurrency. What's more, the general idea -- a language specific to the problem domain -- is quickly growing into a "big thing" and is worth examining in its own right, albeit in a later piece.

Beginnings

The Axum bits for download currently live on the Microsoft Labs Web site at msdn.microsoft.com/en-us/devlabs/dd795202.aspx; be sure to pick up at least both the .msi (either for Visual Studio 2008 or for Beta 1 of Visual Studio 2010) and the Programmer's Guide. Once you install these, fire up Visual Studio, and create a new Axum Console Application project, as shown in Figure 1.


Figure 1 Axum Console Application Project

Replace it with the code shown in Figure 2. This is the Axum "Hello World," which serves the same purpose as every other Hello World application does: to verify that the installation worked and to get a basic sense of the syntax. Assuming everything compiles and runs, we're good to go.

Figure 2 Axum "Hello World"

using System;
using Microsoft.Axum;
using System.Concurrency.Messaging;
namespace Hello
{
public domain Program
{
agent MainAgent : channel Microsoft.Axum.Application
{
public MainAgent()
{
String [] args = receive(PrimaryChannel::CommandLine);
// TODO: Add work of the agent here.
Console.WriteLine("Hello, Axum!");
PrimaryChannel::ExitCode <-- 0;
}
}
}
}

As you can see, the syntax of Axum is highly reminiscent of C#, putting Axum loosely into the category of languages known as the C family of languages: it uses curly braces to denote scope, semicolons to terminate statements, and so on. With that said, however, there are obviously a few new keywords to get to know (domain, agent, channel, receive, just for starters), plus a few new operators. Formally, Axum is not really a derivative of C#, but a selective subset with added new features. Axum includes all C# 3.0 statement types (such as lambdas and inferred local variables) and expressions (such as algebraic calculations and yield return/yield break), methods, field declarations, delegates, and enumeration types, but it leaves out classes, interfaces, or struct/value type definitions, operator declarations, properties, const fields/locals, and static fields/methods.

Hear that whooshing sound? That's the wind rushing past your ears, and if you feel a slight fluttering in your stomach, it's OK. Jumping off cliffs is a good thing.

Concepts and Syntax

The Axum syntax directly reflects the core concepts of Axum, just as it does for C#. Where C# has classes, however, Axum has agents. In Axum, an agent is an entity of code, but unlike a traditional object, agents never directly interact -- instead, they pass messages to one another in an asynchronous fashion through channels, or to be more accurate, across the ports defined on the channel. Sending a message on a port is an asynchronous operation -- the send immediately returns -- and receiving a message on a port blocks until a message is available. This way, threads never interact on the same data elements at the same time, and a major source of deadlock and inconsistency is effectively lifted away.

To see this in action, consider the Hello code. The agent MainAgent implements a special channel, called Application, which is a channel the Axum runtime understands directly -- it will process any incoming command line arguments and pass them over the Application's PrimaryChannel channel, on the port CommandLines. When constructed by the runtime, the MainAgent uses the receive operation (an Axum primitive) to block that port until those arguments are available, at which point execution in the MainAgent constructor continues. The runtime, having done its job, then blocks on the ExitCode port of the PrimaryChannel, which is the signal that the application is finished. Meanwhile, the MainAgent rolls through the array, printing each one, and once finished, sends the integer literal 0 to the ExitCode port. This is received by the runtime, which then terminates the process.

Notice how Axum, from the very beginning, is taking this concurrent execution idea seriously -- not only are developers sheltered from the creation and manipulation of threads or concurrency/locking objects, but the Axum language assumes a concurrent approach even for something as simple as Hello. For something as simple as Hello, arguably, this is a waste of time -- however, most of us don't write applications as simple as Hello very often, and those who do, can always fall back to the traditional object-oriented single-threaded-by-default application language (C# or Visual Basic or whatever else strikes your fancy).

Figure 3 Process Application

using System;
using Microsoft.Axum;
using System.Concurrency.Messaging;
namespace Process
{
public domain Program
{
agent ProcessAgent : channel Microsoft.Axum.Application
{
public ProcessAgent()
{
String [] args = receive(PrimaryChannel::CommandLine);
for (var i = 0; i < args.Length; i++)
ProcessArgument(args[i]);
PrimaryChannel::ExitCode <-- 0;
}
function void ProcessArgument(String s)
{
Console.WriteLine("Got argument {0}", s);
}
}
}
}

Saying Hello Is for Wimps

A slightly more interesting example is the Process application, as Figure 3 shows. In this version, we see a new Axum concept, the function. Functions are different from traditional methods in that the Axum compiler guarantees (by way of compiler error) that the method never modifies the agent's internal state; in other words, functions may not have side effects. Preventing side effects adds to Axum's usefulness as a concurrency-friendly language -- without side effects, it becomes extremely difficult to accidentally write code where a thread modifies shared state (and thus introduces inconsistency or incorrect results).

But we're still not done here. Axum not only wants to make it easier to write thread-safe code, it also wants to make it easier to write thread-friendly code. With two- and four-core machines becoming more mainstream, and 8- and 16-core machines visible on the horizon, it's important to look for opportunities to perform operations in parallel. Axum makes this easier, again by lifting the conversation away from threads and providing some higher-level constructs to work with. Figure 4 shows a modified version of the ProcessAgent.

Two new things are happening here: one, we've created an interaction point, which can serve as a source or a recipient of messages (in this case, it will be both); and two, we've used the forwarding operator to create a pipeline of messages from the interaction point to the ProcessArgument function. By doing this, any message sent to the "arguments" interaction point will be forwarded to ProcessArgument for , well , processing. From there, all that's left is to iterate through the command-line arguments and send each one as a message (via the send operator, the <-- operation inside the loop) to the arguments interaction point, and the work begins.

(Axum formally defines this as a data flow network, and there's much it can do in addition to creating a simple 1-to-1 pipeline, but that's beyond the scope of this introduction. The Programmer's Guide has more details for those who are interested.)

Figure 4 Modified Version of the ProcessAgent

using System;
using Microsoft.Axum;
using System.Concurrency.Messaging;
namespace Process
{
public domain Program
{
agent ProcessAgent : channel Microsoft.Axum.Application
{
public ProcessAgent()
{
String [] args = receive(PrimaryChannel::CommandLine);
// Second variation
//
var arguments = new OrderedInteractionPoint<String>();
arguments ==> ProcessArgument;
for (var i = 0; i < args.Length; i++)
arguments <-- args[i];
PrimaryChannel::ExitCode <-- 0;
}
function void ProcessArgument(String s)
{
Console.WriteLine(“Got argument {0}”, s);
}
}
}
}

By now putting the data through the pipeline, Axum can decide how and when to spin up threads to do the processing. In this simple example, it may not be useful or efficient to do so, but for more sophisticated scenarios, it quite often will. Because the arguments interaction point is an ordered interaction point, the messages sent down the pipeline -- and, more important, the results returned -- will preserve their place in the ordering, even if each message is processed on a separate thread.

What's more, this concept of pipelining messages from one interaction point to another is a powerful concept that Axum borrows from functional languages like F#. Figure 5 shows how easy it is to add some additional processing into the pipeline.

Notice that, again, the function UpperCaseIt is not modifying internal ProcessAgent state but is operating only on the object handed in. Additionally, the pipeline is modified to include UpperCaseIt before the ProcessArgument function, and the intuitive thing happens -- the results of UpperCaseIt are passed into the ProcessArgument function.

As a mental exercise, consider how many lines of C# code would be required to do this, bearing in mind that all of this is also being done across multiple threads, not just a single thread of execution. Now imagine debugging the code to make sure it executes correctly. (Actually, imagining it isn't necessary -- use a tool like ILDasm or Reflector to examine the generated IL. It's definitely not trivial.)

By the way, I have a small confession to make -- as written, my sample doesn't execute correctly. When the preceding code is run, the application returns without displaying anything. This isn't a bug in the Axum bits; this is as intended behavior, and highlights how programming in a concurrent mindset requires a mental shift.

When the arguments interaction point is receiving the command-line arguments via the send operator (<--), those sends are done asynchronously. The ProcessAgent is not blocking when it sends those messages, and therefore if the pipeline is sufficiently complex, the arguments are all sent, the loop terminates, and the ExitCode is sent, terminating the application, all before anything can reach the console.

To fix this, ProcessAgent needs to block until the pipeline has completed operation; it needs to hold the thread alive with something like a Console.ReadLine(). (This turns out to be tricky in practice -- see the Axum team blog for the details.) Or we need to change the way ProcessAgent works, and it's this latter course I'm going to take, primarily in order to demonstrate a few more of Axum's features.

Instead of doing the work inside itself, ProcessAgent will defer the work to a new agent. But now, just to make things a bit more interesting, this new agent will also want to know whether the argument should be uppercase or lowercase. To do this, the new agent will need to define a more complex message, one that takes not only the string to operate on, but also a Boolean value (true for uppercase). To do that, Axum requires that we define
a schema.

Figure 5 Pipelining Messages

using System;
using Microsoft.Axum;
using System.Concurrency.Messaging;
namespace Process
{
public domain Program
{
agent ProcessAgent : channel Microsoft.Axum.Application
{
public ProcessAgent()
{
String [] args = receive(PrimaryChannel::CommandLine);
// Third variation
//
var arguments = new OrderedInteractionPoint<String>();
arguments ==> UpperCaseIt ==> ProcessArgument;
for (var i = 0; i < args.Length; i++)
arguments <-- args[i];
PrimaryChannel::ExitCode <-- 0;
}
function String UpperCaseIt(String it)
{
return it.ToUpper();
}
function void ProcessArgument(String s)
{
Console.WriteLine("Got argument {0}", s);
}
}
}
}

Process my Argument

Implicitly until this point, one of the goals of Axum has been to isolate different executing entities (agents) away from one another, and it has done so by making copies of the messages and handing those to the agent, rather than passing objects directly. No shared state -- even through parameters -- means no accidental chances of thread conflict.

While that works fine for types defined in the .NET Base Class Library, it can easily be defeated if .NET programmers don't do the right thing in user-defined types. To combat this, Axum requires that a new kind of message be defined as a schema type -- essentially an object without the methods and with required and/or optional fields, using the schema keyword:

schema Argument
{
required String arg;
optional bool upper;
}

This defines a new data type, Argument, which consists of a required field, arg, containing the string that is to be made uppercase or lowercase, and an optional field, upper, indicating whether it should be made upper or lower. Now, we can define a new channel with a request/response port that takes Argument messages in and gives String messages back:

channel Operator
{
input Argument Arg : String; // Argument in, String out
}

Having defined this channel, it becomes relatively trivial to write the agent that implements the channel, as shown in Figure 6. Notice that this agent technically never exits its constructor -- it simply spins in a loop, calling receive on the Arg port, blocking until an Argument instance is sent to it, and then sending the result of the ToUpper or ToLower back on the same port (depending on the value of upper, of course).

Figure 6 Agent Implementing Channel

agent OperatingAgent : channel Operator
{
public OperatingAgent()
{
while (true)
{
var result = receive(PrimaryChannel::Arg);
if (result.RequestValue.upper)
result <-- result.RequestValue.arg.ToUpper();
else
result <-- result.RequestValue.arg.ToLower();
}
}
}

From the callers' (users') perspective, using the OperatingAgent is no different than the ProcessAgent itself: we create an instance of it using the built-in method CreateInNewDomain, and start posting Argument instances to the Arg port. When we do, however, an object is returned that will eventually yield the response back from the agent, which is the only way to harvest the results (via another receive() operation), as Figure 7 shows.

When run, it does as expected -- based on the current millisecond at the time of the Argument sent, the command-line string will either be uppercase or lowercase. And, still, all without any direct thread interaction on the part of the developer.

So Little, yet So Far

Axum clearly represents a different way of thinking about programming, and despite its stark differences from C#, implements a significant number of features found in most general-purpose programming languages. Its main purpose, however, centers around concurrency and thread-friendly code, and as such, works best with problems that require (or benefit from) concurrency.

Fortunately, the team building Axum didn't try to reinvent the C# language. Because Axum compiles to .NET assemblies just as other .NET languages do, it's relatively trivial to build the concurrent-heavy parts of the application in Axum (as a Class Library project, instead of a Console Application), and then call into it from C# or Visual Basic. The Axum distribution ships with a sample that does this (the Dining Philosophers sample, illustrating the classic concurrency problem in a WinForms application), and quality time spent with Reflector over the Axum-compiled libraries will reveal a great deal about that interoperability footprint. Using Axum to build library libraries callable from another language (C# or Visual Basic) can go a long way toward making heavy concurrency use vastly more accessible to the other .NET technologies, such as Web or desktop applications.

Figure 7 Using Method CreateInNewDomain

 

agent ProcessAgent : channel Microsoft.Axum.Application
{
public ProcessAgent()
{
String [] args = receive(PrimaryChannel::CommandLine);
var opAgent = OperatingAgent.CreateInNewDomain();
var correlators = new IInteractionSource<String>[args.Length];
for (int i=0; i<args.Length; i++)
correlators[i] = opAgent::Arg <-- new Argument {
arg = args[i],
upper = ((System.DateTime.Now.Millisecond % 2) == 0) };
for (int i=0; i<correlators.Length; i++)
Console.WriteLine("Got {0} back for {1}",
receive(correlators[i]), args[i]);
PrimaryChannel::ExitCode <-- 0;
}
}

In fact, Axum uses an experimental C# compiler that provides some interesting and different features, a superset of C# 3.0 (3.0 + asynchronous methods), none of which is implied for C# 4.0, by the way. What this does permit, however, is the ability to mix-and-match C# and Axum code in the same project, something unique to Axum so far. See the Axum Programmer's Guide for details.Axum has additional features not described here, covered in some detail in the Axum Programmer's Guide, including a close relationship with WCF to make it easier to write high-scale services, but this introduction should be sufficient to get started building applications, libraries or services in Axum. Remember that this is a research project, and users can offer feedback via the Axum Forums on the DevLabs Web site.

What if Axum fails to turn into a shipping product, or readers want to use it before then? For starters, Axum's success or failure depends significantly in part on user reaction, and so will live or die by quality feedback -- send the Axum team your thoughts, and agitate for its release in a public fashion! But even if Axum fails to graduate, remember, the concepts behind Axum are not unique. In the academic phraseology, Axum embodies the Actors model of concurrency, and several other Actors models are available for the .NET developer who wants something for production today. The open-source project "Retlang" embodies several of these concepts, as does the F# language (look at F#'s MailboxProcessor type) or the Microsoft
Concurrency and Control Runtime (CCR), both of which are near-shipping status, if not there already.

In the end, remember, the goal is not to create projects that use every CLR-based language in existence, but to find languages that can solve particular problems and use them when the situation demands.

And remember, Quot linguas calles, tot homines vales.

Ted Neward is the principal with Neward and Associates, through which he speaks, writes and coaches on building reliable enterprise systems. He's written numerous books, taught and spoken at conferences all over the world, is an INETA speaker, and has received the MVP award in three different areas of expertise. Reach him at ted@tedneward.com or through his blog at blogs.tedneward.com.