Thread and Process Agility


Our ASP.NET web service solution for Open, Sesame is scalable and robust. Our console program is not. Let's take a closer look at the program statement within the Open, Sesame console program that seems to be the source of the problem:

 string s = Console.ReadLine(); 


The fundamental issue is that when the Open, Sesame console program calls Console.ReadLine, it will remain stuck there (ignoring failure scenarios) until the relevant stimulus arrives. Dedicating a threada fairly expensive resourceto every instance of this program will make it very hard to build a scalable solution in which multiple instances of Open, Sesame (or similar programs) are allowed to run simultaneously.

One common approach for getting around this problem is to fork the thread of execution by making asynchronous method calls. For example, the ReadLine method can be offered asynchronously as a pair of methods, using a standard .NET Framework pattern:

 public static System.IAsyncResult BeginReadLine(   System.AsyncCallback asyncCallback,   object state ); public static string EndReadLine(System.IAsyncResult ar); 


Inside the BeginReadLine method, a work request can be created and enqueued in the CLR's work queue. This request will be serviced asynchronously by a thread drawn from the CLR thread pool, but in the meantime the BeginReadLine method will have returned to its caller.

The thread that calls BeginReadLine can poll the System.IAsyncResult object's IsCompleted property until the result is available, or can use the AsyncWaitHandle property to wait (as shown here), which is more efficient:

 using System; class Program {   static void Main()   {     // Print the key     string key = DateTime.Now.Millisecond.ToString();     Console.WriteLine("here is your key: " + key);     IAsyncResult result = BeginReadLine(null, null);     result.AsyncWaitHandle.WaitOne();     string s = EndReadLine(result);     // Print the greeting if the key is provided     if (key.Equals(s))       Console.WriteLine("hello, world");   } } 


Although this program uses asynchronous invocation, it still consumes a thread by calling WaitOne on the IAsyncResult object.

We can start to see a way out of this dilemma by looking at the last two parameters of BeginReadLine. Asynchronous methods in the .NET Framework allow the caller of the Begin method to provide a delegate of type System.AsyncCallback (along with an object that holds any state[1] shared by the callback and the code invoking the Begin method; in our examples, this is null) with the expectation that the callee will signal the completion of the asynchronous operation by invoking this delegate.

[1] In a manner not unlike the weakly typed session state variable we used in the web service.

Here's Open, Sesame written using the AsyncCallback mechanism:

 using System; using System.Threading; class Program {   static string key;   static void Main()   {     // Print the key     key = DateTime.Now.Millisecond.ToString();     Console.WriteLine("here is your key: " + key);     BeginReadLine(ContinueAt, null);     Thread.Sleep(Timeout.Infinite);   }   static void ContinueAt(IAsyncResult ar)   {     string s = EndReadLine(ar);     // Print the greeting if the key is provided     if (key.Equals(s))       Console.WriteLine("hello, world");     Environment.Exit(0);   } } 


Although this program still ties up the initial thread (this time by doing an infinite sleep), the use of an asynchronous callback allows some of our program logic to run on arbitrary threads. The ContinueAt method will run on a different thread than Main.

This version of Open, Sesame achieves a measure of thread agility in a very simple way. We use an asynchronous callback to separate, but link together, chunks of the program's execution logic. As important, we have changed the way we implement the variable key that is used across stages of the program's execution. Rather than using a stack-based local variable as in our original program, key is now a static field that is visible across methods. This stackless approach is the key to reducing our program's dependence on a given thread, while maintaining strongly typed data declarations.

Bookmarks

We've now seen that we can make (some of) our Open, Sesame program "thread agile" by using asynchronous method calls and AsyncCallback. We've done nothing, however, to make our program "process agile" which we know is a requirement for scalability and robustness.

The approach outlined previously (using AsyncCallback) does show promise. The delegate specifying the ContinueAt method acts like a bookmarka logical location in the program's execution at which the execution can be resumed when appropriate stimulus arrives. This provides us with some leverage:

  1. We can give bookmarks names, and also provide a manager of bookmarks.

  2. We can make bookmarks serializable, which will allow them to be saved to, and loaded from, durable storage.

  3. We can write a listener program that acts as a single point of entry for data that needs to be delivered to any of the bookmarks in our system.

Let's begin by defining a class called Bookmark that is essentially a named wrapper for a delegate of a custom type, BookmarkLocation:

 [Serializable] public class Bookmark {   public Bookmark(string name, BookmarkLocation continueAt);   public string Name { get; }   public BookmarkLocation ContinueAt { get; }   public object Payload { get; }   public BookmarkManager BookmarkManager { get; } } public delegate void BookmarkLocation(Bookmark resumed); 


Bookmarks are managed by the BookmarkManager:

 public class BookmarkManager {   public void Add(Bookmark bookmark);   public void Remove(Bookmark bookmark);   public void Resume(string bookmarkName, object payload); } 


A bookmark is a continuationan object representing a program frozen in action. The physical continuation point of the program is specified by the bookmark's ContinueAt property. The bookmark is named so that it can be referred to and manipulated independent of the physical continuation point (which may be shared by multiple bookmarks). BookmarkManager simply maintains a set of bookmarks. When the BookmarkManager.Resume method is called, the appropriate program is resumed at the location indicated by the bookmark. The input (stimulus) that accompanies the resumption is made available to the program via the Payload property of the resumed bookmark.

We marked the Bookmark class as [Serializable] so that the bookmark manager can save all pending bookmarks to durable storage such as a database. In this way, wherever and whenever the BookmarkManager is instantiatedso long as it has access to the durable storage device (for example, by being provided with a database connection string)it can reliably obtain its set of bookmarks. When a reactive program is stuck, awaiting stimulus, we can passivate itsave it to durable storageas a set of bookmarks.

To enable entire programs to participate in bookmarking, we need to eliminatenot just reduceour dependence on the stack. This means we cannot have a Main method that calls Thread.Sleep as our most recent implementation of Open, Sesame does. Instead, we will write an OpenSesame class that has instance methods and heap-allocated state.

Here is our new implementation of Open, Sesame:

 [Serializable] public class OpenSesame {   string key;   public void Start(BookmarkManager mgr)   {     // Print the key     key = DateTime.Now.Millisecond.ToString();     Console.WriteLine("here is your key: " + key);     mgr.Add(new Bookmark("read", ContinueAt));   }   void ContinueAt(Bookmark resumed)   {     string s = (string) resumed.Payload;     BookmarkManager mgr = resumed.BookmarkManager;     mgr.Remove(resumed);     // Print the greeting if the key is provided     if (key.Equals(s))       Console.WriteLine("hello, world");   } } 


The Open, Sesame program is now embodied by an object of type OpenSesame. It is easy to create such an object and "run" it (the Start method stands in for Main) within the context of a listener[2] that waits for input to arrive:

[2] The listener can obtain input from anywherea database, a data source like an MSMQ queue, a web service or web application, or even the trusty console. This logic is now decoupled from the Open, Sesame program.

 BookmarkManager mgr = new BookmarkManager(); OpenSesame openSesameProgram = new OpenSesame(); openSesameProgram.Start(mgr); ... string str = // obtain input mgr.Resume("read", str); ... 


Resumable Program Statements

Although the preceding code is workable for a single instance of a single program, we would be much better served by a runtimean execution environment for programs like our OpenSesame classthat can help us manage multiple instances of a variety of such programs.

We shall call this execution environment the mythical runtime:

 public class MythicalRuntime {   // Starts a new program   public ProgramHandle RunProgram(ProgramStatement program);   // Returns a handle to a previously started program   public ProgramHandle GetProgramHandle(Guid programId);   // Passivates all in-memory programs   public void Shutdown(); } public class ProgramHandle {   // Unique identifier for this program   public Guid ProgramId { get; }   // Passivate the program   public void Passivate();   // Resume a bookmarked program   public void Resume(string bookmarkName, object payload); } 


The mythical runtime allows us to run a ProgramStatement object.

It's time to formalize the notion that the OpenSesame class is indeed a new kind of program. Given the sensible requirement that OpenSesame should be usable (as a single statement) within more complex programs (comprised of multiple statements), we will call the OpenSesame class a resumable program statement.

The ProgramStatement type standardizes an entry point for execution, and serves as the base class for all resumable program statements:

 [Serializable] public abstract class ProgramStatement {   public abstract void Run(BookmarkManager mgr); } 


Given the definition of ProgramStatement, the OpenSesame class is revised like this:

 [Serializable] public class OpenSesame : ProgramStatement {   string key;   public override void Run(BookmarkManager mgr)   {     // Print the key     key = DateTime.Now.Millisecond.ToString();     Console.WriteLine("here is your key: " + key);     mgr.Add(new Bookmark("read", ContinueAt));   }   // ContinueAt same as earlier, elided for clarity   ... } 


A ProgramStatement object that is provided to the RunProgram method of the mythical runtime is a resumable program. When we give such an object to the mythical runtime, it becomes something more than just an ordinary CLR object. The object is now a transient, in-memory representation of a resumable program. Because the resumable program can be passivated, it has a logical lifetime that is potentially longer than that of the initial ProgramStatement object and the CLR application domain in which this object is created.

Every program managed by the mythical runtime has a globally unique identifier that can be obtained from the ProgramHandle object returned by the MythicalRuntime.RunProgram method. The identifier can be passed between machines so that a passivated program can be brought back into memory via MythicalRuntime.GetProgramHandle, anywhere that the durable storage medium in which passivated programs are stored is available.

Because the execution logic of our resumable program uses bookmarking, it awaits stimulus without blocking a thread when it reaches a point at which an external entity must provide input. When this happens, the mythical runtime can passivate the program. When relevant data arrives, the program is brought back into memory and resumed (via ProgramHandle.Resume). The resumption of the passivated program might happen 47 weeks later on a different machine.

We can now sketch out generic listener code that listens for input, determines the program to which that input is addressed,[3] and resumes that program at the appropriate bookmark:

[3] In practice, the listener can also decide that a given input should trigger the creation of a new program. In this case, MythicalRuntime.RunProgram is invoked instead of ProgramHandle.Resume.

 MythicalRuntime runtime = new MythicalRuntime(); ... while (true) {   // receive some input   object input = ...   // determine from the input which program to use   ProgramHandle handle = runtime.GetProgram(...)   // determine from the input which bookmark to resume   string bookmarkName = ...   handle.Resume(bookmarkName, input); } 


A given listener implementation obviously has latitude for deciding how to map inputs to appropriate bookmarks within programs. But that's a good thingour goal is to support different conceptual models[4] on top of the general-purpose bookmarking substrate we are building; the mythical runtime is not coupled to any particular implementation of a listener.

[4] For example, web-based programming models such as ASP.NET map incoming requests to programs using logic that relies upon session identifiers (similar to ProgramHandle.ProgramId) and a static model for describing program resumption points (web service operation names are like bookmark names).

We have succeeded in establishing a flexible bookmarking capability and a runtime for resumable programs. This is a general-purpose framework with two important characteristics:

  • Bookmarked programs do not block threads while they await stimulus.

  • Bookmarked programs can reside in durable storage, as continuations, while they await stimulus.

To summarize our progress so far, we have leveraged the idea of bookmarks to enable the writing of resumable programs that can be passivated. Because these programs maintain their state in serializable fields, they can be passivated and represented faithfully in durable storage as continuations. These programs now have both thread agility and process agility. Furthermore, the general nature of the bookmarking model means that we can employ it in the development of a variety of reactive programs, web-based and otherwise.




Essential Windows Workflow Foundation
Essential Windows Workflow Foundation
ISBN: 0321399838
EAN: 2147483647
Year: 2006
Pages: 97

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net