Language Support for Asynchronous Programming

I’ve been taking some traditional sorts of programs lately and making them asynchronous, using thread pools.  I had to do this because the default ThreadPool limit is 20 threads per processor, and once you have 20 blocking threads, your whole app stops.


This basically means taking code like this:



void doIt()
{
  Socket s = new Socket(…)
  s.Connect(“hello.com”, 123);
  s.Write(…)
  s.Read(…)
  s.Close()
}


And turning it into something like..



void doIt()
{
  Socket s = new Socket(…);
  asyncResult = s.BeginConnect(“hello.com”, 123, connectDoneDelegate);
  allDone.WaitOne();
  if (someException != null)
    throw someException;
}

void connectDoneDelegate(IAsyncResult result)
{
  try
  {
    s.EndConnect(result);
    s.BeginWrite(…, writeDoneDelegate)
  } catch (ex)
  {
    someException = ex;
    allDone.Set();
  }
}

void writeDoneDelegate(IAsyncResult result)
{
  s.EndWrite(result);
  s.BeginRead(…, readDoneDelegate);
}

void readDoneDelegate(IAsyncResult result)
{
  s.EndRead(result);
  allDone.Set();
}


Doing this will make the code much more scalable (since you can have thousands of these operations in progress with just a few worker threads), but at a pretty high price:



  • The readability of the algorithm is gone.

  • The algorithm has become orders of magnitude more difficult to debug.

  • I didn’t include exception handling in all the async handlers, but I did for the connectDoneDelegate.  This might not be the way you want to implement your object (since you probably want the doIt function to be asynchronous as well) but something has to be done to get the exceptions back to the caller.  This means catching them and holding them until you know that throwing them will propagate up to the caller.

Why do this at all?  In a nutshell, threads are expensive.  The biggest expense with a thread is the stack – a megabyte (by default) of address space.. two thousand threads and you’ve consumed 2gb of address space.  Presumably you want your server app to someday handle more than a few thousand simultaneous connections, but even if you don’t, the OS generally doesn’t handle having thousands of threads around very gracefully.