Synchronization in COmega

I’ve had some real fun tonight playing with the COmega compiler from MS Research. If you’re at all interested in programming languages and haven’t downloaded the compiler yet, you owe it to yourself to check it out. This project is much farther along than the F# compiler released last year. The MSR folks have implemented a full VS.NET language service (using the VSIP Babel API’s, I presume). You get VS.NET project support, syntax highlighting, and limited Intellisense. Plus, you get a .chm file that’s pretty extensive for a research project. Overall, the development experience is quite good.

Anyway, on to the interesting stuff. COmega has done a number of really cool things. Aside from implementing the X#/Xen stuff around XML and SQL integration, they’ve incorporated the concurrency syntax from the Polyphonic C# research effort. Polyphonic C# is a set of language-level syntactical primitives designed to make asynchronous programming easier by lifting support for various synchronization primitives out of the API and into the language itself. 

The concurrency extensions in COmega add a couple of new syntactical elements to the C# language – async’s and chords. An async is a cross between a method and a counting semaphore. Like methods, async’s can be called. However, async’s don’t declare a set of body statements – instead, they are strictly a blocking primitive that behaves similarly to a counting semaphore. Every time you “call” an asyc, you stick a message on its queue. Operations can wait on an async – if a message exists in the queue, it will be dequeued and the operation will proceed. If there are no more messages left in the async, the operation will block until one appears (just like blocking on a semaphore until it becomes signaled). Blocking on an async is accomplished through the use of chords.

A chord is a language-level representation of things that must occur simultaneously (the way a chord is composed of several simultaneous notes in music – hence the “Polyphonic” nature of Polyphonic C#). Here’s a very simple example of asyncs and chords taken from the COmega docs: 

public class Buffer 
{
   public async Put(string s);
   public string Get() & Put(string s) { return s; }
}
 

Here we have a simple class that defines one async ( Put ) and one chord (Get). The “Get() & Put( string s)” construct means that anytime somebody calls Get(), the operation will block until a message appears on the Put() async. Once that happens, the body of Get() will execute. It’s interesting to note that the body of the Get() chord has access to the string passed to the Put() async, even though Get() itself is called with no parameters.

Using asyncs and chords, you can do some very interesting things. This, for example, is the equivalent of a C# lock statement: 

//The lock object
async syncRoot(); 

//Aquire the lock by waiting on syncRoot()
public DoSomthing() & syncRoot()
{
       MyThreadSafeFunc();
       //release the lock
       syncRoot();
}           

Using asyncs and chords, it’s possible to implement constructs similar to Thread.Join. The COmega has a pretty good discussion of how to do this (see the “Asynchronous Call and Return Patterns” tutorial), but it doesn’t cover anything like WaitForSingleObject() or WaitForMultipleObjects(). They’re pretty easy to do yourself, though.

Of the two, WaitForSingleObject ( a.k.a “Select” ) is the easiest to implement. Basically, you define a class to represent the Select operation. This class defines a private async member (which the WaitOne() operation will block on) and a callback delegate that external tasks can use to invoke this async when they’re done with their work. Here’s my implementation of Select: 

public class Select
{

  //Tasks will invoke this async via the CompletionCallback delegate when they’re done

  async ProcessCompleted( object result );      

  private TaskCompletionCallback completionCallback;

  public Select()
  {

     //Set up the CompletionCallback member. Tasks will invoke
     //this callback (and thus signal the ProcessCompleted async)
     //when they're done with their work.

     completionCallback = new TaskCompletionCallback( this.ProcessCompleted );
  }   

  public TaskCompletionCallback CompletionCallback
 
{
   
get{ return this.completionCallback; }
 
}

  //The WaitOne() operation will block until the ProcessCompleted async
  //gets signaled. Effectively, it will unblock after the first task
  //completes – the other tasks will still complete, but the thread
  //waiting on WaitOne() will get unblocked once the first task is done.  

  public object WaitOne() & ProcessCompleted( object result )
  {
      
Console.Out.WriteLine( "Done: " + result.ToString());
      
Console.Out.WriteLine( "" );

      
return result;
  
}
}  

Here’s how it’s used in practice:

Random r = new Random();
Task task0 = new Task( r.Next( 10 ) );
Task task1 = new Task( r.Next( 10 ) );

Console.Out.WriteLine( "Waiting for one of two processes to finish via Select" );

Select @select   = new Select();
StringBuilder sb = new StringBuilder();

task0.Execute( sb, @select.CompletionCallback );
task1.Execute( sb, @select.CompletionCallback ); 

//This will unblock once either task0 or task1 is done,
//depending on which one completes first.

StringBuilder results = (StringBuilder) @select.WaitOne(); 

It’s possible to extend the pattern exhibited by Select into a more general WaitForN. Instead of returning once one task completes, we wait until a specified number of tasks complete. To do this, we add another async – TaskCompleted(). The handler for the async keeps track of how many outstanding taks are done, and if it reaches a threshold value it singles the completion of the whole process by posting a message to the ProcessCompleted async. Here’s my implementation of WaitForN:

public class WaitForN
{

   //This signals that all N tasks are done
   async ProcessCompleted( object result ); 

   //This signals that a single task has completed
   async TaskCompleted( object result );   

   async syncRoot();

   private TaskCompletionCallback completionCallback; 

   private int n;
   
private int maxN;

   public WaitForN( int n )
  
{
      
this.n = 0;
  
    this.maxN = n;          

       completionCallback = new TaskCompletionCallback( this.TaskCompleted );
      
      
//Need to prime the lock with a message, so the first call
       //to the locked statements doesn’t deadlock
      
syncRoot();
   
}

    public TaskCompletionCallback CompletionCallback
    {
       get{ return this.completionCallback; }
    }   

    //This blocks until ProcessCompleted fires
   
public object WaitN() & ProcessCompleted( object result )
   
{
   
   return result;
    
}

    //This is a completely asynchronous chord. It gets called
    //on a new thread whenever a message gets posted to the TaskCompleted async.
    //It’s locked via syncRoot() to guarantee only one thread can be in this function
    //at a time.
    when TaskCompleted( object result ) & syncRoot()
    {
       this.n++;
       Console.Out.WriteLine( n + " tasks completed." );   

       if( n == maxN )
          ProcessCompleted( result ); 

       //Surrender the lock
       syncRoot();
          
    }
}

A natural response to this stuff is “how is this any better than the way I’m used to doing things?” At this point, I’m not really sure if it is or isn’t. It’s definitely different – it offers new idioms and affords new ways of expressing old idea. Over time, we’ll come to a conclusion as to whether this really makes any sense from a practical standpoint. For now, though, I think it’s ok for COmega to be simply “new and cool”. It is, after all, a research language and not yet a product. However, I don’t think I’d mind if something like this showed up in the Orcas SKUs J