Previous Page
Next Page

The WF Runtime

The WF runtime is the execution environment for WF programs. In order to use the WF runtime, it must be hosted in a CLR application domain. This amounts to nothing more than instantiation of a System.Workflow.Runtime.WorkflowRuntime object. We refer to the application that instantiates the WF runtime as the host application.

The WorkflowRuntime type is shown in Listing 2.13.

Listing 2.13. WorkflowRuntime

namespace System.Workflow.Runtime
                        {
                        public class WorkflowRuntime
                        {
                        public WorkflowRuntime();
                        public void AddService(object service);
                        public void RemoveService(object service);
                        public void StartRuntime();
                        public void StopRuntime();
                        public WorkflowInstance CreateWorkflow(XmlReader reader);
                        public WorkflowInstance GetWorkflow(Guid instanceId);
                        /* *** other members *** */
                        }
                        }
                        

We can run a WF program by invoking the WorkflowRuntime.CreateWorkflow method with a System.Xml.XmlReader that provides access to XAML. CreateWorkflow returns an object of type System.Workflow.Runtime.WorkflowInstance. A WorkflowInstance is a handle to a WF program instance.

The WorkflowInstance type is shown in Listing 2.14.

Listing 2.14. WorkflowInstance

namespace System.Workflow.Runtime
                        {
                        public sealed class WorkflowInstance
                        {
                        public Guid InstanceId { get; }
                        public void Start();
                        public void Load();
                        public void Unload();
                        public void EnqueueItem(IComparable queueName,
                        object item, IPendingWork pendingWork, object workItem);
                        /* *** other members *** */
                        }
                        }
                        

Listing 2.15 is a console application that hosts the WF runtime. The console application uses the WF runtime to run one instance of the Open, Sesame program defined in Listing 2.11.

Listing 2.15. Hosting the WF Runtime and Running a WF Program

using System;
                        using System.Workflow.ComponentModel.Compiler;
                        using System.Workflow.Runtime;
                        using System.Xml;
                        namespace EssentialWF.Host.Chapter2
                        {
                        class Program
                        {
                        static void Main()
                        {
                        using(WorkflowRuntime runtime = new WorkflowRuntime())
                        {
                        TypeProvider typeProvider = new TypeProvider(runtime);
                        typeProvider.AddAssemblyReference("EssentialWF.dll");
                        runtime.AddService(typeProvider);
                        runtime.StartRuntime();
                        WorkflowInstance instance = null;
                        using (XmlTextReader reader =
                        new XmlTextReader("OpenSesame.xoml"))
                        {
                        instance = runtime.CreateWorkflow(reader);
                        instance.Start();
                        }
                        string s = Console.ReadLine();
                        instance.EnqueueItem("r1", s, null, null);
                        // Prevent Main from exiting before
                        // the WF program instance completes
                        Console.ReadLine();
                        runtime.StopRuntime();
                        }
                        }
                        }
                        }
                        

The WF runtime must be told where to find the activity types that are used in the Open, Sesame XAML, and that is the job of the TypeProvider.

When the Start method is called on the WorkflowInstance, the WF runtime runs the WF program instance asynchronously (on a different thread than the one on which Main is running). Other threading models are supported by the WF runtime, and are discussed in Chapter 5.

When a ReadLine activity executes, it creates a WF program queue. When our console application (which is playing the role of a listener) reads a string from the console, it resumes the execution of the bookmark established by the ReadLine by enqueuing the string. The name of the WF program queue is the same name, "r1", that we gave to the ReadLine activity (per the execution logic of ReadLine).

Passivation

A reactive program like Open, Sesame is characterized by episodic executionit performs small bursts, or episodes, of work that are punctuated by relatively long idle periods spent waiting for stimulus from an external entity. During these idle periods, the WF program instance is inactive because it cannot make forward progress and the WF runtime can passivate itmove it from memory to persistent storage such as a database. The instance will be resumed, possibly in a different process on a different machine, when relevant stimulus arrives. This is illustrated in Figure 2.1.

Figure 2.1. WF program execution is episodic and distributed.

 

 


In order to support the passivation of WF program instances, the WF runtime must be configured to use a persistence service. WF includes a default persistence service, named SqlWorkflowPersistenceService, that uses SQL Server as the durable storage medium for WF program instances. As we will see in Chapter 5, it is possible to plug in a custom persistence service that uses whatever storage medium is appropriate for your solution.

In order to use the SqlWorkflowPersistenceService in our solution, we of course must install SQL Server and create a database table that will be used to store passivated WF program instances.[1] To enable passivation, we can modify the console application logic to instantiate the SqlWorkflowPersistenceService and add it to the WF runtime:

[1] The Windows Workflow Foundation SDK contains instructions and SQL scripts for setting up the SQL Server database tables used by the SqlWorkflowPersistenceService.

using(WorkflowRuntime runtime = new WorkflowRuntime())
            {
            SqlWorkflowPersistenceService persistenceService =
            new SqlWorkflowPersistenceService(...);
            runtime.AddService(persistenceService);
            ...
            }
            

In order to illustrate the mechanics of passivation, we can write two different console applications. The first one, shown in Listing 2.16, begins the execution of an instance of the Open, Sesame program.

Listing 2.16. Passivating a WF Program Instance

using System;
                        using System.Workflow.ComponentModel.Compiler;
                        using System.Workflow.Runtime;
                        using System.Workflow.Runtime.Hosting;
                        using System.Xml;
                        namespace EssentialWF.Host.Chapter2
                        {
                        class FirstProgram
                        {
                        static string ConnectionString =
                        "Initial Catalog=SqlPersistenceService;Data
                        Source=localhost;Integrated Security=SSPI;";
                        static void Main()
                        {
                        using (WorkflowRuntime runtime = new WorkflowRuntime())
                        {
                        SqlWorkflowPersistenceService persistenceService =
                        new SqlWorkflowPersistenceService(ConnectionString);
                        runtime.AddService(persistenceService);
                        TypeProvider typeProvider = new TypeProvider(runtime);
                        typeProvider.AddAssemblyReference("EssentialWF.dll");
                        runtime.AddService(typeProvider);
                        runtime.StartRuntime();
                        WorkflowInstance instance = null;
                        using (XmlTextReader reader =
                        new XmlTextReader("OpenSesame.xoml"))
                        {
                        instance = runtime.CreateWorkflow(reader);
                        instance.Start();
                        }
                        Guid durableHandle = instance.InstanceId;
                        // save the Guid...
                        instance.Unload();
                        runtime.StopRuntime();
                        }
                        }
                        }
                        }
                        

The WF program instance never completes because it is expecting to receive a string after it prints the key, and we do not provide it with any input. When the WorkflowInstance.Unload method is called,[2] the instance is passivated. Inspection of the SQL Server database table that holds passivated WF program instances will show us a row representing the idle Open, Sesame program instance.

[2] The call to Unload is not strictly necessary in this case since WorkflowRuntime.StopRuntime unloads all instances, but invocation of Unload makes our intention clearer.

In order to resume the passivated instance in another CLR application domain, we need to have some way of identifying the instance. That is precisely the purpose of the InstanceId property of WorkflowInstance. This globally unique identifier can be saved and then later passed as a parameter to the WorkflowRuntime.GetWorkflow method in order to obtain a fresh WorkflowInstance for the WF program instance carrying that identifier.

This is exactly what we will do in a second console application, shown in Listing 2.17.

Listing 2.17. Resuming a Passivated WF Program Instance

using System;
                        using System.Workflow.ComponentModel.Compiler;
                        using System.Workflow.Runtime;
                        using System.Workflow.Runtime.Hosting;
                        using System.Xml;
                        namespace EssentialWF.Host.Chapter2
                        {
                        class SecondProgram
                        {
                        static string ConnectionString =
                        "Initial Catalog=SqlPersistenceService;Data
                        Source=localhost;Integrated Security=SSPI;";
                        static void Main()
                        {
                        using (WorkflowRuntime runtime = new WorkflowRuntime())
                        {
                        SqlWorkflowPersistenceService persistenceService =
                        new SqlWorkflowPersistenceService(ConnectionString);
                        runtime.AddService(persistenceService);
                        TypeProvider typeProvider = new TypeProvider(runtime);
                        typeProvider.AddAssemblyReference("EssentialWF.dll");
                        runtime.AddService(typeProvider);
                        runtime.StartRuntime();
                        // get the identifier we had saved
                        Guid id = ...
                        WorkflowInstance instance = runtime.GetWorkflow(id);
                        // user must enter the key that was printed
                        // during the execution of the first part of
                        // the Open, Sesame program
                        string s = Console.ReadLine();
                        instance.EnqueueItem("r1", s, null, null);
                        // Prevent Main from exiting before
                        // the WF program instance completes
                        Console.ReadLine();
                        runtime.StopRuntime();
                        }
                        }
                        }
                        }
                        

The passivated (bookmarked) WF program instance picks up where it left off, and writes its result to the console after we provide the second string.


Previous Page
Next Page