Composition


Our work is not done. The resumable OpenSesame program definitely does not have the control flow we are seeking. If we revisit our original Open, Sesame console program, we are reminded that it is a sequence of program statements:

 static void Main()  {   // Print the key   string key = DateTime.Now.Millisecond.ToString();   Console.WriteLine("here is your key: " + key);   string s = Console.ReadLine();   // Print the greeting if the key is provided   if (key.Equals(s))     Console.WriteLine("hello, world");  } 


We can use the standard technique of modularity here and decompose the resumable OpenSesame program into smaller units that each have the same basic structure as OpenSesame. This factoring will allow programs other than OpenSesame to make use of these building blocks.

The Read class shown here is a bookmark-savvy replacement for the Console.ReadLine statement in the original Open, Sesame console program:

 [Serializable] public class Read : ProgramStatement {   private string text;   public string Text   {     get { return text; }   }   public override void Run(BookmarkManager mgr)   {     mgr.Add(new Bookmark("read", ContinueAt));   }   void ContinueAt(Bookmark resumed)   {     text = (string) resumed.Payload;     BookmarkManager mgr = resumed.BookmarkManager;     mgr.Remove(resumed);   } } 


The Read class has a property, Read.Text, so that the caller of Read.Run can access the string that is provided as the payload of the resumed bookmark. But how does the caller know when the value of the Read.Text property is available? We can use a bookmark to notify the caller of Read.Runwhen the Read.ContinueAtmethod completes.

In our implementation of Read, we rely on a bookmark in order to allow execution to pause without blocking a thread and later resume when relevant input is provided by an external entity. By using exactly the same bookmarking approach for internal notification, the caller of the Read.Run method can acquire the same ability to pause, and then resume when Read has completed its execution!

Here is a slightly modified Read class (the implementation of the Text property remains exactly the same, and is elided for clarity):

 [Serializable] public class Read : ProgramStatement {   // Text property elided for clarity...   private string outerBookmarkName;   public Read(string outerBookmarkName)   {     this.outerBookmarkName = outerBookmarkName;   }   public void Run(BookmarkManager mgr)   {     mgr.Add(new Bookmark("read", ContinueAt));   }   public void ContinueAt(Bookmark resumed)   {     text = (string) resumed.Payload;     BookmarkManager mgr = resumed.BookmarkManager;     mgr.Remove(resumed);     mgr.Resume(outerBookmarkName, this);   } } 


Read accepts the name of an "outer" bookmark as a parameter to its constructor. Read holds on to this bookmark name and, after the text field is set (in the ContinueAt method), the BookmarkManager is asked to resume the "outer" bookmark. The Read has logically completed its execution, and its caller is being notified of this fact.

Program Statement Lifecycle

The technique for internal notifications shown previously works fine but there is no reason why BookmarkManager cannot nicely support this simple asynchronous notification pattern with an internally managed bookmark. We will explain the mechanics of this internal bookmark in a minute, but first let's look at a simplified Read class:

 [Serializable] public class Read : ProgramStatement {   // Text property elided for clarity...   public override void Run(BookmarkManager mgr)   {     mgr.Add(new Bookmark("read", ContinueAt));   }   public void ContinueAt(Bookmark resumed)   {     text = (string) resumed.Payload;     BookmarkManager mgr = resumed.BookmarkManager;     mgr.Remove(resumed);     mgr.Done();   } } 


Instead of worrying about an "outer" bookmark, the Read program statement simply informs the bookmark manager when its execution is complete. The other half of this pattern surfaces in the caller of Read; in our case, this is the OpenSesame program:

 [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.RunProgramStatement(new Read(), ContinueAt);   }   public void ContinueAt(Bookmark resumed)   {     Read read = (Read) resumed.Payload;     string s = read.Text;     // Print the greeting if the key is provided     if (key.Equals(s))       Console.WriteLine("hello, world");     mgr.Done();   } } 


We have decomposed the logic of OpenSesame by using a Read object to obtain the required string. Instead of calling the Read.Run method directly, OpenSesame now calls BookmarkManager.RunProgramStatement and provides the resumption point for a bookmark that will be managed internally by BookmarkManager. The bookmark manager invokes Read.Run on behalf of OpenSesame. The internal bookmark is never surfaced to Read, which reports its completion by calling BookmarkManager.Done. When its Done method is called, BookmarkManager resumes the (internally created) bookmark that was established during the prior invocation of BookmarkManager.RunProgramStatement. At this point, OpenSesame can access the Read.Text property (the Read object is passed, for convenience, as the payload of the resumed bookmark).

The BookmarkManager class now looks like this:

 public class BookmarkManager {   public void Add(Bookmark bookmark);   public void Remove(Bookmark bookmark);   // Request execution of a program statement, using an   // implicit bookmark that will be resumed when that   // program statement completes its execution   public void RunProgramStatement(ProgramStatement statement,     BookmarkLocation continueAt)   // Indicate that the current program statement is done,   // so that internally managed bookmarks can be resumed   public void Done(); } 


Now that we have established a well-defined beginning (ProgramStatement.Run) and ending (BookmarkManager.Done) for the execution of any resumable program statement, we can express program statement lifecycle in terms of the automaton (also known as a finite state machine) shown in Figure 1.1.

Figure 1.1. Program statement automaton


When a program statement is created, it is in a "Latent" state, waiting to be run. When its Run method is invoked, the program statement moves to the "Running" state. The program statement remains in the "Running" state for an indeterminate amount of time until it asynchronously reports its completion.

As we will see in the next section, the introduction of an automaton that describes the lifecycle of any program statement sets the stage for general-purpose control flow.




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