The Joins Concurrency Library
Overview

The research language promised C# users a more pleasant world of concurrent programming. Cω presents a simple, declarative and powerful model of concurrency - join patterns - applicable both to multithreaded applications and to the orchestration of asynchronous, event-based distributed applications. Using Generics, we can now provide join patterns as a .NET library rather than a language extension. We call this library Joins for short. The library works with C#, but also VB and other .NET languages.

At the heart of Joins is the Join class. This class provides a mostly declarative, type-safe mechanism for defining thread-safe asynchronous and synchronous communication channels and patterns, for use by concurrently executing local threads or remote processes. The communication channels are values of special delegate types initialized from a common join object. Communication and/or synchronization takes place by invoking these delegates, passing arguments and optionally waiting for replied return values. The allowable communication patterns as well as their effects are declared using join patterns: bodies of code whose execution is guarded by linear combinations of channels. The body, or continuation, of a join pattern is provided by the user as a delegate that may manipulate external resources protected by the join object.

Here's a simple example of using C# 2.0 and the Joins library to declare a string buffer for shared communication between threads:

 
 class Buffer {
   public readonly Asynchronous.Channel<string> Put;
   public readonly Synchronous<string>.Channel Get;
   public Buffer() {
     Join join = Join.Create();
     join.Initialize(out Put);  
     join.Initialize(out Get);
     join.When(Get).And(Put).Do(delegate(string s) { 
       return s; 
     });
   }
 }

This code declares a buffer class with two communication channels. The Put field contains an asynchronous channel whose delegate Invoke method takes a string argument and returns void (immediately). The Get field contains a synchronous channel whose delegate Invoke method returns a string but takes no argument. A producer thread asynchronously sends a string by calling Put(s), never waiting. A consumer thread synchronously requests a string by calling Get(), possibly waiting. The join pattern constructed by the call to join.When declares that a caller of Get may wait until/or unless there has been some matching call to Put, in which case the argument s of that Put is consumed (atomically) and returned to the caller. Since this is the only pattern involving Get, a caller of Get must wait for a matching Put(s).

The Joins library has much wider application than just encoding buffers. In general, a channel may be involved in multiple join patterns and a pattern may join any number of channels. Additionally, patterns that join only asynchronous channels may be used to conditionally spawn new threads. Join patterns support natural encodings of traditional concurrency constructs like locks and semaphores as well as higher-level abstractions such as Futures and Active Objects. The library can also be used in distributed applications, e.g. using .NET Remoting.

Download and Resources

The Joins binaries are available as an MSR download. Installation requires the .NET Framework 2.0. The download includes a tutorial, reference documentation, samples and demos. Some flavour of Visual Studio 2005 is recommended for building samples.

A separate paper and slide deck describe the implementation of the Joins library.

Credits

The Joins library was written by Claudio Russo, adapting techniques proposed by Nick Benton, Cédric Fournet and Luca Cardelli for the implementation of Polyphonic C# and . It would not have been possible without Andrew Kennedy and Don Syme's pioneering work on Generics for .NET.