The Continuations Class

In this posting I am going to discuss the central class used to support continuations, the Continuation class.  The implementation I will discuss in this posting is minimalist in nature, providing the bare essentials to support continuations.  It is likely in the future I will post augmentations of this class, if for no other reason than to address cross cutting concerns such as error handling and security, but for the moment these would distract from the central issue.

 

An instance of a continuation associates an identifier with a persisted delegate, which is the continuation itself, and provides a means to pass data to the continuation when it is resumed.  In essence, when you want to establish a continuation, you create a delegate representing where you want control to go, you assign a unique identity to this continuation that some external entity can generate, and you optionally establish logic to handle information passed in when the continuation is resumed, i.e. the persisted flow of control resumes running.

 

Here is the class that defines continuations.  Glance over this and then I’ll discuss the relevant bits in the remainder of the post.

 

namespace WorkflowManager

{

    public delegate void BookmarkLocation(Continuation resumed);

    [Serializable]

    public class Continuation

    {

        private string _identity;

        private object _data;

        private BookmarkLocation _continuation;

        //[NotSerializable]

 

        public Continuation(string name,BookmarkLocation continuation)

        {

            _identity = name;

            _continuation = continuation;

        }

 

        public void Resume(object data)

        {

            _data = data;

            _continuation(this);

        }

        public string Identity

        {

            get { return _identity; }

        }

 

        public object Data

        {

            get { return _data; }

            set { _data = value; }

        }

 

        public BookmarkLocation ContinuationPoint

        {

            get { return _continuation; }

        }

    }

}

 

The first thing to note is the definition of the delegate, which provides the form for all the continuations you will use.  Note that it accepts a single argument, which represents the continuation that contained the delegate definition, i.e. the delegate has a fundamentally self referential nature.  Consider the following usage example;

 

namespace BookmarkDemo

{

    [Serializable]

    class StockCheckRequest

    {

        private string _partNumber;

        public StockCheckRequest(string partNumber)

        {

            _partNumber = partNumber;

            Form1._mainDisplay.WriteConsole("Checking stock on" + _partNumber);

            Form1._continuationManager.Add(new Continuation("stockcheck_response_" + _partNumber, CheckCompleted));

        }

 

        public void CheckCompleted(object checkResult)

        {

            bool result = (bool) checkResult;

            if(result)

            {

                Form1._mainDisplay.WriteConsole(_partNumber + " in stock");

                Form1._mainDisplay.WriteConsole("Continuing order");

            }

            else

            {

                Form1._mainDisplay.WriteConsole(_partNumber + " out of stock");

                Form1._mainDisplay.WriteConsole("Cancelling order");

            }

        }

    }

}

 The StockCheckRequest method uses a continuation to preserve its flow of control.  Imagine that you had an inventory system which used an automated warehouse management system, such that any attempt to determine the number of items stocked for a particular piece of merchandise involved a call to this external system.  Your operating flow would effectively be idle while the call was in progress, i.e. in a traditional SOA architecture you’d need to hang around and wait and hope nothing went wrong in the interim.  If you created and persisted a continuation, then you could define an architecture where you could issue the request and then forget all about it, waiting for the continuation to be triggered by some external entity, at which point you could resume operation with the result data.  With this pattern, not only have you not blocked an execution thread waiting for a result, you don’t even have to be executing.

 

If you examine the function, you will see that the _partNumber instance variable is being initialized, and then a continuation is being generated.  Ignoring the issue of continuation management (I’ll get into that in a later posting), you should note two key points;

  • A unique name is being generated for the continuation that couples action, state, and identity together.  In this example, we’re fundamentally associating the continuation with the response to a stock check request for a specific part number.
  • The delegate itself is an instance method of a StockCheckRequest instance.  This is a key element because the serialization mechanism for a delegate will automatically serialize the instance of a class that the delegate is bound to, provided that the delegate is bound to an instance member function of the class, and the class itself is serializable.  You’ll note that the StockCheckRequest class is marked as serializable, and that the bound member function, CheckCompleted, is in fact an instance member function.

 

The product of these two points is that, when deserialized, the delegate will point to the reconstructed instance of the class, i.e. any information that has been stored in instance member variables that was serializable will be restored to its original form..

 

At this point, given that the continuation manager instance and all the continuations it maps are serializable (they are), if this were serialized to persistent storage,  the program could terminate, and resume at some later point, deserializing the persisted continuation manager and the continuations it contains.  If you then received a message back from the stock checking system about the amount of stock on hand, you could generate the name of the associated continuation, and resume operation.

 

In the next posting I will focus on the resumption of a continuation, which involves the identification of a specific continuation and the invocation of the Resume method shown in the class above.  In this manner, a program can

  1. Run to a certain point
  2. Establish a continuation
  3. Persist the continuation
  4. Terminate
  5. Restart
  6. Resurrect the continuation
  7. Resume operation at the point marked by the continuation.

 

This isn’t to say that a program has to follow these steps, i.e. terminating between the persistence and resurrection of a continuation, and it certainly doesn’t limit programs to a single continuation, it only illustrates that continuations effectively detach the logical flow of control from the executing process and thread.  A completely different machine can resurrect a persisted continuation, and the flow of control will move forward as it had never been halted at all.

Published Thursday, April 19, 2007 9:32 PM by MarkMMullin
Filed Under: ,

Comments