Hour 17 Learning Advanced Hosting

What You’ll Learn in This Hour:

Image   Workflow events

Image   Runtime services

Image   ManualWorkflowSchedulerService and threading

Image   InvokeWorkflow activity

Image   Host retention with the InvokeWorkflow activity

Image   InvokeWorkflow activity synchronous augmentation

WF is not a server product. The WorkflowRuntime type can be hosted in any .NET 2.0 or later Windows application. Host applications communicate with the WorkflowRuntime through a set of events it exposes. The WorkflowRuntime’s capabilities are defined via pluggable runtime services. The pluggable runtime services are managed through a set of WorkflowRuntime methods. There is also a WorkflowInstance type that operates on a one-to-one basis with a workflow instance. It is, however, primarily a helper object that works through the workflow runtime.

Hour 3, “Learning Basic Hosting,” briefly discussed the WorkflowRuntime and WorkflowInstance types. Hour 3 and other hours also covered selected runtime services and events as they became relevant. This hour summarizes all the runtime events and services. It then covers selected individual runtime events and services that have not yet been covered (or have been sparsely covered) in more detail. Threading and synchronous workflow loading are covered in a ManualWorkflowSchedulerService example.

The hour concludes by going through a new activity, the InvokeWorkflow activity, which allows a workflow to be called from another workflow. This activity is covered in this hour because its asynchronous operation creates many hosting considerations. Some of these considerations include keeping the host alive until the called workflow completes and ways to call workflows synchronously. Examples are shown that allow synchronous-like operation with the InvokeWorkflow activity and that keep the host alive.

Exploring WorkflowRuntime Events

All the workflow events—for example, Started, Loaded, Unloaded—are summarized in the following list. Then a sample application is used that illustrates the workflow instance life cycle as the instance transitions among events. The application also allows the user to manually execute the Suspended, Resumed, Aborted, Terminateed, WorkflowCreated, and Started events to see the effect of these events. Many of WF’s workflow events have already been covered throughout the book and especially in Hour 3, where the Persist, Load, and Unload events were covered.

Image   ServicesExceptionNotHandledOccurs when a runtime service registered with the WorkflowRuntime raises an unhandled exception.

Image   StartedOccurs when the workflow runtime is started.

Image   StoppedOccurs when the workflow runtime is stopped.

Image   WorkflowAbortedOccurs when a workflow instance is aborted. The WorkflowInstance.Abort method may be called from the host. Aborting a workflow discards all work since the last persistence (save) point. The workflow instance is still active when it is returned to the persistence point.

Image   WorkflowCompletedOccurs when a workflow instance successfully completes.

Image   WorkflowCreatedOccurs when a workflow instance has been created—more precisely, when WorkflowInstance.CreateWorkflow is called, which occurs before the workflow is actually started. The Initialized method, called when the workflow is created, is generally used to perform preparatory work, such as to set up a custom queue for any activity (see Hour 21, “Creating Queued Activities,” and Hour 22, “Creating Typed Queued and EventDriven-Enabled Activities.”

Image   WorkflowIdledAs seen in Hour 3, the workflow idled event is triggered when the workflow has to wait before continuing to process, because the next activities in its path are blocking activities, such as HandleExternalEvent and Delay.

Image   WorkflowLoadedOccurs when the workflow instance is loaded back into memory by the persistence service and before the workflow begins reexecuting.

Image   WorkflowResumedccurs when execution of a workflow instance is resumed following a suspension. It is also called when dynamic update is performed before the workflow begins reexecuting.

Image   WorkflowStartedOccurs when a WorkflowInstance.Start method is called to start workflow execution.

Image   WorkflowSuspendedOccurs when the WorkflowInstance.Suspend method is called, when the Suspend activity is executed, and when the workflow runtime suspends the workflow during dynamic update. The workflow will remain suspended until the WorkflowInstance.Resume method is called, unless it is suspended by the runtime during dynamic update, wherein the workflow is resumed during the update process.

Image   WorkflowTerminatedOccurs when the WorkflowInstance.Terminate method is called, when the Terminate activity is executed, and when an unhandled exception occurs on a workflow. Unlike aborted workflows, terminated workflows are complete and cannot be restarted.

Image   WorkflowUnloadedOccurs when a workflow goes idle and the unload method is called from the idled event handler. Alternatively, workflows are unloaded when a workflow reaches an idle state and a persistence service is configured to unload workflows in an idle state registered with the host.

Image

The ServicesExceptionNotHandled, Started, and Stopped events operate on the workflow runtime. All other events are handled by the workflow runtime but are specific to a workflow instance. These workflow instance-level events are mediated by the runtime so it can ensure that the workflow is interacted with on the correct thread and that the workflow is ready to receive the event.

Workflow Events Sample Application

A Windows Forms application and a workflow are used in this section to demonstrate workflow events. The workflow event handlers update the form display so that the workflows event transition can be followed. There are also buttons that permit you to manually Suspend, Resume, Abort, Terminate, Create, and Start a workflow. Because you have already registered events and created event handlers, you will not do that again here. Running the application is the exercise. Figures 17.1 and 17.2 show the form and workflow that will be used.

FIGURE 17.1 Form used to demonstrate events.

Form used to demonstrate events.

FIGURE 17.2 Workflow used to demonstrate events.

Workflow used to demonstrate events.

The workflow (Figure 17.2) is structured to send messages to the Windows Form that contain the name of the currently executing activity. The CallExternalMethod activities both send the current activity to the form. The Delay activity causes the workflow to unload during its brief timeout duration. The code activities both sleep for a couple of seconds to give you a chance to interact with the form buttons while the workflow remains on a given activity.

Running the AdvancedHostingForms Project

You will run the workflow four times in this section. The first time, you run it through its standard course. Then you will abort, suspend, and terminate the workflow.

Running the Workflow in Its Normal Course

Follow the next steps to run the workflow in its normal course.

1.   Open the EventHandlerSolution.sln in the C:SamsWf24hrsHoursHour17AdvancedHostingEventHandlerSolution directory.

2.   Press F5 to run the application.

3.   Click the CreateWorkflowInstance button. The status changes to Created, and the workflow instance ID text box is populated (Figure 17.3). The workflow is created but not yet started. In this state, workflow queues are updated (see Hours 21 and 22) and other preparatory work takes place.

FIGURE 17.3 Workflow in created state.

Workflow in created state.

4.   Click the StartNewWorkflowInstance button. The workflow is now started (or running). See Figure 17.4.

FIGURE 17.4 Workflow running.

Workflow running.

5.   The workflow will run through the activities. It will be unloaded when the Delay activity is reached. It will then reload after the Delay activity expires and execute the remaining activities until it completes (Figure 17.5).

FIGURE 17.5 Workflow in completed state.

Workflow in completed state.

Image

The Current Activity text box on the form is populated with Before Delay, Probably Delay, or After Delay. The Before Delay and After Delay messages are sent from the CallExternalMethod activities before and after the Delay activity. The Delay activity cannot communicate with the form, so it is assumed that when the workflow is in the unloaded state it is in the Delay activity (thus, Probably Delay).

Suspending and Resuming the Workflow

Follow the next steps to suspend and resume the workflow.

1.   Click the CreateWorkflowInstance button and then click the StartNewWorkflowInstance button. Click the Suspend button and the workflow will transition (it may take a few seconds) to the suspended state.

2.   Click the Resume button and the workflow will complete.

Aborting the Workflow

Abort returns the workflow to its last persistence state. Therefore its only valid execution point is when executing the activities that follow the Delay activity. The Delay will cause the workflow to unload.

Follow the next steps to abort the workflow.

1.   Click the CreateWorkflowInstance button and then click the StartNewWorkflowInstance button.

2.   Wait until the workflow is executing the activities that follow the Delay activity and click the Abort button. The activities that follow the Delay activity will execute again. The abort caused the workflow execution to go back to the persistence point. The workflow then completes.

Image

The SQL workflow persistence service is registered with the runtime, and its UnloadOnIdle property is set to true. If this were not the case, the workflow would not automatically restart after the Delay activity expired.

Terminating the Workflow

Follow the next steps to terminate the workflow.

1.   Click the CreateWorkflowInstance button and then click the StartNewWorkflowInstance button.

2.   At any point, click the Terminate button and the workflow is terminated. Termination results in workflow completion, unlike abort, which goes back to the last persistence point.

Runtime Services

WF has to run pretty much anywhere Windows runs. This means that it has to run on client PCs with limited resources, on fault-tolerant server applications, and at many points between these two extremes. The large footprint it must support also produces unpredictability. It cannot assume that SQL Server, or any database for that matter, will be available to persist, which is most likely the case in many client scenarios. The need, then, is to create a workflow runtime that can be absorbed into various computing environments.

A large part of the solution is runtime services. Runtime services can be registered with the runtime to change the runtime. For instance, registering the persistence service with the WF runtime changes the WF runtime behavior to save workflows to a storage medium when they idle. The default persistence service saves workflows to SQL Server. You can, however, create your own persistence service that stores the workflows to XML files or to an Oracle database. The first is a good solution in many client scenarios, the latter in environments where Oracle is used.

The workflow runtime can run with no persistence service. It can run with the default SQL Persistence service. Or it can run with a custom persistence service. The end result is a runtime capable of supporting just about any persistence requirements. The persistence, tracking, and ExternalDataExchangeService services have been used extensively throughout the book to change the capabilities of the underlying runtime.

Other runtime services exist as well. Some control how WF schedules workflow instance, or WF’s threading. Others determine how WF loads a workflow model into memory. You can create your own runtime services as well. One common use case for a custom runtime service is to create a common library (a type provider) that can then be accessed by workflow instances.

All WF runtime services inherit from the base WorkflowRuntimeService base class that provides general runtime service capabilities applicable to any runtime service. On top of the WorkflowRuntimeService is a class specific to one type of runtime service. For instance, the TrackingService provides functionality needed by all tracking services, and the WorkflowLoaderService is the base class for all loading services. The SqlTrackingService can be used as is to store tracking data in SQL Server. The DefaultWorkflowLoaderService (described shortly) loads workflows into memory. The SqlTrackingService is sealed, and the DefaultWorkflowLoaderService is not.

Therefore, in most cases, if you were to create a custom tracking service, you would derive the TrackingService base class. If you want to create a custom workflow loading service, you can derive from either the base WorkflowLoaderService or the DefaultWorkflowLoaderService, depending on which one your requirements align with better.

Image

As stated before, if you are interested in creating custom runtime services, see the tracking and persistence samples in the SDK.

Some runtime services are loaded by default. The DefaultWorkflowSchedulerService, for instance, is loaded by the runtime if no other scheduler service is registered. So, this service has been with us on all the workflows we’ve run, even though it has not been explicitly registered. Other runtime services, such as the tracking service, allow for multiple entry registration. Table 17.1 contains a listing and a few details on each of the runtime services.

TABLE 17.1 WF Runtime Services

Image

Image

Let’s first look at the out-of-the-box (OOB) runtime services offered by WF that have not been previously used (or used only sporadically).

Loading Service

The DefaultWorkflowLoaderService loads workflows, and it is employed by default. Therefore, unless you create your own workflow loading service, no action on your part is required. Your hosts will load and run workflows. However, just about everything in WF is extensible, including the way it loads a workflow to process. The default loading service can load either a .NET type or a XAML workflow (and a .rules file).

The WorkflowLoaderService (the base loading service class) can accept either a .NET type or an XML text reader. If you derive your own custom loader from the WorkflowLoaderService type, you can serialize any XML document and run it in WF. One of the benefits of XAML (or any XML) workflows is that they can be stored in a database. A custom loader could be created to retrieve the XAML workflow directly from a database. Another excellent reason for a custom loader is to directly load Visio diagrams into WF. Visio diagrams can be saved to XML, so the job of the custom loader would be to deserialize the Visio XML format into an object that WF could load. WF does this for XAML. For other XAML formats, you must create the object graph. See my blog at www.reassociates.net for an example that loads Visio diagrams from a database into WF where they are executed.

Workflow Queuing Service

All host-workflow communication operates on top of WF’s internal workflow queues. All high level WF communication, including ExternalDataExchangeService, web services, and Windows Communication Foundation, is built on the WorkflowQueuingService. The WF queuing service is implemented by .NET delegates and is not a message queuing system (MSMQ). The low-level queuing was used briefly when you worked with state machine workflows and will be used more in Hours 22 and 23.

Scheduling (Threading) Services

WF ships with two scheduling services: the DefaultWorkflowSchedulerService and the ManualWorkflowSchedulerService. Both queue workflow instances and process them when a thread is made available. The biggest difference is that the default scheduling service runs workflows asynchronously, whereas the manual scheduling service runs them synchronously.

The default scheduling service uses CLR threadpool threads to run workflow instances on. Depending on the server configuration, about five runtime instances can be loaded and executing at any given time. This means actively running workflow instances. Persisted workflows do not count. You can control the number by setting its MaxSimultaneousWorkflows property.

Image

Even if you have thousands of long-running workflows, it is unlikely that a large number of them will receive an external event or have a timer expire at the exact same time. Only during their execution bursts do workflows take a thread (assuming you have a persistence service registered). You can also have multiple WF runtime servers and multiple back-end persistence servers for scalability and durability reasons.

The ManualWorkflowSchedulerService runs workflows synchronously. In this case, a processing workflow blocks the host during the workflow’s execution burst. The thread is returned to the host as soon as the workflow unloads, suspends, terminates, or otherwise completes. The host cannot run any more workflows until the workflow unloads from memory. This manual scheduling service is generally considered the correct choice when hosting the WF runtime in IIS because IIS has its own thread reuse mechanisms and it has very few spare threads. For these reasons, this service is sometimes used synonymously with using WF in ASP.NET (see Hour 18, “Working with Web Services and ASP.NET Hosting”).

When using your Windows Forms application to host workflows in earlier chapters, you employed anonymous delegates to communicate across the workflow and the host thread. The Windows Forms application needed to host only one application at a time. It executed quickly enough that blocking would not adversely affect the UI. It actually would be preferable if it blocked, because it would prevent the user from trying to activate another event while the current one is processing. Therefore, employing the manual scheduling service from your Windows Forms host may have been preferable.

If you used a Windows Service application to host the WF runtime that fronted multiple Windows Forms hosts, the default scheduling would seem more logical because there would be a benefit to simultaneously process multiple workflows instances from different Windows Forms hosts.

Using the manual scheduling service will be demonstrated next.

Image

All runtime services can be added via configuration. See Hour 3 for an example of general steps involved. The SqlWorkflowPersistenceService is used, but most of the steps to create a config file and how to access it are identical across all services. Then see MSDN for the precise details on the service you want to add via configuration.

Building the Scheduling Service Project

A workflow console project will be used to demonstrate the differences between the DefaultWorkflowSchedulerService and the ManualWorkflowSchedulerService. The console host will run three workflows. A variable in the host will determine which scheduling service is used. When running the application, you will see that each workflow executes in serial when the manual scheduling service is used and in parallel when the default scheduling service is used.

Ironically, a Delay activity is not used to cause the workflows to pause to make the workflows take longer to run and therefore make it easier to spot when they start and end. The Delay activity causes the workflow to unload, and they then appear to execute in parallel even when the manual scheduling service is used because of the short execution bursts (as will be demonstrated in this section).

A console application exists that will run three workflows.

Modeling and Configuring the Workflow

Follow these steps to model and configure the workflow.

1.   Create a new Sequential Workflow Console Application project named SchedulingServiceProject and place it in the C:SamsWf24hrsHoursHour17AdvancedHosting directory.

2.   Add two Code activities to the workflow.

3.   Double-click the first Code activity and add the following code to its handler:

                    string instanceId = this.WorkflowInstanceId.ToString();
                    Console.WriteLine
                        ("Workflow " + instanceId + " is in the first Code
activity at " +
DateTime.Now.TimeOfDay);
                    Thread.Sleep(5000);

4.   Right-click Thread and select Resolve, using System.Threading.

5.   Double-click the second Code activity and add the following code to its handler:

                    string instanceId = this.WorkflowInstanceId.ToString();
                    Console.WriteLine
                        ("Workflow " + instanceId + " is in the second Code
activity at "
                        + DateTime.Now.TimeOfDay);

                    Thread.Sleep(5000);

Updating the Host to Run Three Workflows

Follow the next steps to update the host to run three workflow instances.

1.   Open Program.cs.

2.   Add the following code to create a static variable (below the class’s opening bracket).

        static bool RunManual = false;

3.   Replace the code that creates and starts the workflow instance with the following that starts three workflow instances.

                        WorkflowInstance instance1 =
workflowRuntime.CreateWorkflow(typeof(SchedulingServiceProject.Workflow1));
                        instance1.Start();

                        WorkflowInstance instance2 =
workflowRuntime.CreateWorkflow(typeof(SchedulingServiceProject.Workflow1));
                        instance2.Start();

                        WorkflowInstance instance3 =
workflowRuntime.CreateWorkflow(typeof(SchedulingServiceProject.Workflow1));
                        instance3.Start();

4.   Add the following code to pause the host (below the waitHandle.WaitOne(); line of code).

                        Console.WriteLine("Press Enter to continue.");
                        Console.Read();

Running the Workflow with the DefaultWorkflowSchedulerService

The DefaultWorkflowSchedulerService will be used because it is the default scheduling service and no other was specified. Each workflow’s first Code activity will start at almost the same time, and the workflows will complete at near the same time as well, because all workflows run in parallel.

Follow the next steps to run the three workflow instances in parallel.

1.   Press F5 to run the application.

2.   You will see results similar to Figure 17.6 as the workflows run in interleaved fashion.

FIGURE 17.6 Parallel workflow execution.

Parallel workflow execution.

Updating the Host to Use the ManualWorkflowSchedulerService

After switching to the ManualWorkflowSchedulerService, the first workflow will run until completion, then the second will run all the way through, and finally, the third will do the same. When instantiating the manual scheduler service, you will tell it to automatically reload when timers expire. The ManualWorkflowSchedulerService.RunWorkflow method must be used to run workflows under the manual scheduler service after the starting the workflow. The WorkflowRuntime.GetAllServices method will also be used to return the registered runtime services.

Follow the next steps to configure the host to run the workflows sequentially.

1.   Open Program.cs.

2.   Change RunManual to true.

3.   Add the following code to the top of the Using block to instantiate the manual scheduling service.

                        ManualWorkflowSchedulerService manualScheduler =
                            new ManualWorkflowSchedulerService(true);

4.   Add the following code below the code you just added to load the manual scheduling service if RunManual is true:

                        if (RunManual)
                                    workflowRuntime.AddService(manualScheduler);

5.   Replace the code that creates and starts the workflow instances with the following that includes the extra step required by the manual scheduling service to run the workflow:

                        WorkflowInstance instance1 =
workflowRuntime.CreateWorkflow(typeof(SchedulingServiceProject.Workflow1));
                        instance1.Start();
                        if (RunManual)
                            manualScheduler.RunWorkflow(instance1.InstanceId);

                        WorkflowInstance instance2 =
workflowRuntime.CreateWorkflow(typeof(SchedulingServiceProject.Workflow1));
                        instance2.Start();
                        if (RunManual)
                            manualScheduler.RunWorkflow(instance2.InstanceId);

                        WorkflowInstance instance3 =
workflowRuntime.CreateWorkflow(typeof(SchedulingServiceProject.Workflow1));
                        instance3.Start();
                        if (RunManual)
                            manualScheduler.RunWorkflow(instance3.InstanceId);

6.   Add the following code below the code that creates and starts the workflows that use the WorkflowRuntime.GetAllServices method to display all the services registered with the runtime.

                        Console.WriteLine();
                        Console.WriteLine("Registered Services");

                        // Retrieve registered runtime servies.
                        ReadOnlyCollection<object> services =
                            workflowRuntime.GetAllServices(typeof(object));
                        for (int i = 0; i < services.Count; i++)
                        {
                           Console.WriteLine(services[i].ToString());
                        }

7.   Right-click ReadOnlyCollection, select Resolve, using System.Collections.ObjectModel.

Running the Workflow with the ManualWorkflowSchedulerService

Follow the next steps to run the workflows in serial and see which runtime services are registered.

1.   Press F5 to run the application.

2.   You will see results similar to Figure 17.7 as the workflows run in serial.

FIGURE 17.7 Serial workflow execution.

Serial workflow execution.

As you can see, the default scheduler and default loader services are registered in addition to the manual scheduler service. The first two are registered by default.

Modifying the Workflow to Use Delay Activities

The Thread.Sleep statements in the Code activities do not trigger WF’s unload and persist events. Therefore, they produce workflows that block for long periods of time within an activity. Follow the next steps to remove the Thread.Sleep statements from the Code activities and insert a Delay activity between the Code activities. The workflow—even though still using the manual scheduling service—will appear to run in parallel, because control is returned to the host each time the Delay activity begins and the workflow unloads.

1.   Add the following static variable below the RunManual declaration:

        static string connectionString = "Initial Catalog=
WFTrackingAndPersistence;" +
                    "Data Source=localhost; Integrated Security=SSPI;";

2.   Add the following code to instantiate and register the persistence service at the beginning of the Using block:

                        // Persistence parameter values
                        bool unloadOnIdle = true;
                        TimeSpan reloadIntevral = new TimeSpan(0, 0, 0, 20, 0);
                        TimeSpan ownershipDuration = TimeSpan.MaxValue;

                        // Instantiate a sql persistence service
                        SqlWorkflowPersistenceService sqlPersistenceService =
                           new SqlWorkflowPersistenceService(connectionString,
                           unloadOnIdle, ownershipDuration, reloadIntevral);

                        workflowRuntime.AddService(sqlPersistenceService);

3.   Remove the Thread.Sleep lines of code from both Code activity handlers.

4.   Add a Delay activity between the two Code (see Figure 17.8) activities and set its timeout duration to 00:00:10.

FIGURE 17.8 Manual scheduling service with Delay activities causes parallel-like execution.

Manual scheduling service with Delay activities causes parallel-like execution.

5.   Run the workflow and (as shown in Figure 17.9) it should appear to run in parallel even though the manual scheduling service is used. The SqlWorkflowPersistenceService should also be listed as one of the services.

FIGURE 17.9 Manual scheduling service with Delay activities causes parallel-like execution.

Manual scheduling service with Delay activities causes parallel-like execution.

InvokeWorkflow Activity

The InvokeWorkflow allows the current workflow to call another workflow in the current project or a referenced assembly. Configuring the activity itself is trivial; simply point it to a workflow in the current project or a referenced assembly. The issue, however, is that it calls the workflow asynchronously, and it is up to you to keep the host running while the invoked workflow executes, unless your scenario calls for fire-and-forget. If not, the host will prematurely complete and the called workflow will be aborted before completing. Most of the effort involved with using the InvokeWorkflow activity revolves around understanding the hosting implications. The next exercise will show how to use the InvokeWorkflow activity in fire-and-forget mode and then check that all workflows have finished executing before the host expires, thereby terminating any running workflows.

You will go through three exercises with the InvokeWorkflow activity. The first uses a standard host, and the host expires before the called workflow completes. The second shows one way to configure the host to ensure that it remains active until all workflows started by it complete. The third method emulates a synchronous call and passes parameters between the workflows. The third method requires quite a bit of code to achieve a synchronous call.

A third-party activity exists that allows workflows to be called synchronously. The third-party activity can be found at www.masteringbiztalk.com/blogs/jon/PermaLink,guid,33cfb35c-aca7-4a5e-8b35-ff983b0b83e4.aspx. This activity is not covered here, but is recommended if you need to perform synchronous calls. You will configure synchronous calls the hard way in this hour, so you will have that to fall back on.

InvokeWorkflow Project: Host Prematurely Exits

In the next subsection you create a workflow that runs and prematurely exits and in the following subsection you run the workflow in this configuration.

Create Simple Workflow with InvokeWorkflow Activity

Follow the next steps to create CallingWorkflow, which will call an already created CalledWorkflow using an InvokeWorkflow activity. The CalledWorkflow contains two Code activities that display identifying messages, and each has Thread.Sleep(30000) statements.

1.   Open the InvokeWorkflowSolution solution in C:SamsWf24hrsHoursHour17AdvancedHostingInvokeWorkflowSolution.

2.   Right-click the project and select Add, Sequential Workflow.

3.   Name the workflow CallingWorkflow and click Add.

4.   Add a Code activity, double-click it, and add the following code to its handler:

                    Console.WriteLine("Calling Workflow " +
this.WorkflowInstanceId.ToString() +
                         " is executing in code activity 1");

5.   Add another Code activity below the Code activity, double-click the new Code activity, and add the following code to its handler:

                    Console.WriteLine("Calling Workflow " +
this.WorkflowInstanceId.ToString() +
                        " is executing in code activity 2");

6.   Add an InvokeWorkflow activity between the two Code activities.

7.   Click the ellipsis in its TargetWorkflowProperty, select CalledWorkflow, and click OK.

8.   Your workflow should now look like Figure 17.10, with the CalledWorkflow visible in the InvokeWorkflow activity.

FIGURE 17.10 Modeled CalledWorkflow.

Modeled CalledWorkflow.

Image

The InvokeWorkflow activity should show the called workflow. However, my experience is that it sometimes shows the workflow and at other times instructs to compile to see the called workflow. If it shows that the workflow is bound in the InvokeWorkflow activity TargetWorkflow parameter, it is probably okay.

Running the Fire-and-Forget Configuration

When running the workflow, the host will expire as soon as the calling workflow completes. It will not wait for the called workflow because it’s invoked asynchronously. Both the calling and called workflows share the same host and workflow runtime. It is up to you, the host author, to ensure the host remains in scope until both workflows complete. To the workflow runtime, both workflows are the same. Again, it is up to you to add logic to the host to make sure it remains in scope as long as it needs to run the workflows spawned from it.

Follow the next step to run the workflow.

1.   Press F5 to run the workflow and you will get results similar to Figure 17.11. (Even if you wait a couple of minutes, the second Code activity in the Called workflow will not execute.)

FIGURE 17.11 Host prematurely completes.

Host prematurely completes.

InvokeWorkflow Project: Host Waits

Luckily, a pretty easy solution exists. The WorkflowRuntime provides a GetLoadedWorkflows method that returns all loaded workflows. Checking that there are 0 workflows in both the completed and terminated event handlers ensures there are no active workflows. You may also want to insert the check for active workflow in the persisted handler, but that depends on your implementation.

Follow the next steps to configure the host to wait until the called workflow completes.

1.   Add the following code to the end of the WorkflowCompleted handler:

                    Console.WriteLine("Workflow completed: " +
e.WorkflowInstance.InstanceId.ToString());
                    if
(e.WorkflowInstance.WorkflowRuntime.GetLoadedWorkflows().Count == 0)
                       waitHandle.Set();

2.   Add the following code to the end of the WorkflowTerminated handler:

                    if
(e.WorkflowInstance.WorkflowRuntime.GetLoadedWorkflows().Count == 0)
                        waitHandle.Set();

3.   Run the workflow again, and both the calling and called workflows should run until completion (Figure 17.12):

FIGURE 17.12 Host completes both workflows before exiting.

Host completes both workflows before exiting.

InvokeWorkflow Project: Synchronous Calls and Parameters

This option supports performing a synchronous-like call between the workflows. The InvokeWorkflow activity is still used to call the workflow, and it still does so asynchronously. A HandleExternalEvent activity is added below the InvokeWorkflow activity that waits for the called workflow to send completion notification. This mimics a synchronous call because the calling workflow does not move beyond the HandleExternalEvent activity until the called workflow triggers the event when it is ready. Calling the workflow synchronously may also eliminate the need to check that all workflows are complete in the workflow runtime completed handler, if the CallExternalMethod activity performing the notification is the last activity in the called workflow.

Parameters are passed from the InvokeWorkflow to public properties on the called workflow. The CallingWorkflow can receive data back via the HandleExternalEvent activity as it waits for response from the CalledWorkflow.

Creating the Local Service Project

Follow the next steps to add a local service class that carries the status from the called to the calling workflow. It also includes the calling workflow instance ID for reasons described shortly.

1.   Add a new Class Library project to the solution and name it InvokeWorkflowLocalServices.

2.   Rename Class1.cs to IInvokeWorkflowLocalService.cs in the Solution Explorer. (If you are asked to rename all references in the project to reflect the name change, click Yes.)

3.   Right-click the LocalService project in the Solution Explorer, select Add, Class, and name it CompletedWorkflowEventArgs.cs.

4.   Add a reference to the workflow trio as well (System.Workflow.Activities, System.Workflow.Runtime and System.Workflow.ComponentModel).

Creating the Local Service Interface

Follow the next steps to code the local service.

1.   Open IInvokeWorkflowLocalService.cs.

2.   Replace the entire file contents with the following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Workflow.Activities;

namespace InvokeWorkflowLocalServices
{
    [ExternalDataExchange]
    public interface IInvokeWorkflowLocalService
    {

        void CommunicateWorkflowStatus(Guid callingWorkflowInstancedId,
string
workflowStatus);
        event EventHandler<CompletedWorkflowEventArgs> WorkflowCompleted;
    }
}

Creating the EventArgs Class

Follow the next steps to code the EventArgs class.

1.   Open CompletedWorkflowEventArgs.cs and replace the entire file contents with the following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Workflow.Activities;

namespace InvokeWorkflowLocalServices
{
    [Serializable]
    public class CompletedWorkflowEventArgs : ExternalDataEventArgs
    {
        private string workflowStatus;
        public string WorkflowStatus
        {
                    get { return workflowStatus; }
                    set { workflowStatus = value; }
        }

        // Instance Id is the calling workflow instance id
        public CompletedWorkflowEventArgs(Guid instanceId, string
workflowStatus)
                    : base(instanceId)
        {
                    this.workflowStatus = workflowStatus;
        }
    }
}

2.   Right-click the InvokeWorkflowLocalServices project and select Build. Fix errors, if any.

Adding Parameters to the InvokeWorkflow Activity

The following steps are required to pass parameters using the InvokeWorkflow activity:

1.   Create a dependency property in the CalledWorkflow named CallingWorkflowInstanceId. Public properties and dependency properties in the called workflow are available as parameters to the invoke workflow activity.

2.   Create the CallingWorkflowInstanceId field in the calling workflow. This is the value that will be bound to the invoke workflow activity parameter and then passed to the called workflow.

3.   Store the WorkflowInstanceId to the CallingWorkflowInstanceId field in the InvokeWorkflow activity Invoked handler.

Behind the scenes you are using WorkflowParameterBinding. You used it previously when passing parameters via dictionary object from the host. These public properties and dependency properties are accessed by way of the InvokeWorkflow activity rather than through a dictionary object.

1.   Open CalledWorkflow and switch to the code view.

2.   Right-click below the class opening bracket, select Insert Snippet, double-click Other, double-click Workflow, and double-click DependencyProperty—Property to insert a new dependency property. You should see the stub dependency property, and the text MyProperty should be highlighted.

3.   Replace the highlighted MyProperty text with CallingWorkflowInstanceId, press Tab, and set its type to Guid. Then press Enter to exit the wizard.

4.   Open CallingWorkflow and click the InvokeWorkflow activity. Its Properties window should now contain a CallingWorkflowInstanceId property as shown in Figure 17.13. (If it does not show, build the InvokeWorkflowProject.)

FIGURE 17.13 Invoke workflow activity now has CallingWorkflowInstanceId property.

Invoke workflow activity now has CallingWorkflowInstanceId property.

5.   Click the ellipsis in the CallingWorkflowInstanceId property of the InvokeWorkflow activity. Select the Bind to New Member tab. Enter CallingWorkflowInstanceId as the name, and choose Field. Then click OK.

6.   Enter InvokeWorkflowInvoking in the InvokeWorkflow activity’s Invoking property. Then press Enter and enter the following code in the handler:

                    CallingWorkflowInstanceId = this.WorkflowInstanceId;

Updating the Calling and Called Workflows

Follow the next steps to add activities to the calling and called workflows.

1.   Add a HandleExternalEvent activity to the CallingWorkflow below the InvokeWorkflow activity.

2.   Add a CallExternalMethod activity to the end of the CalledWorkflow.

Configuring the CallExternalMethod Activity

Follow the next steps to configure the CallExternalMethod activity.

1.   Open the CalledWorkflow in design mode.

2.   Add a project reference from the InvokeWorkflowProject to the InvokeWorkflowLocalServices project.

3.   Click the CallExternalMethod activity’s InterfaceType property, select the IInvokeLocalService interface, and click OK.

4.   Select the CommunicateWorkflowStatus method from the MethodName drop-down.

5.   Click the ellipsis in the CallingWorkflowInstanceId property of the CallExternalMethod activity. Select the CallingWorkflowInstanceId property and click OK.

6.   Enter Completed in the workflowStatus property.

Configuring the HandleExternalEvent Activity

Follow the next steps to configure the HandleExternalEvent activity.

1.   Open the CallingWorkflow in design mode.

2.   Click the HandleExternalEvent activity’s InterfaceType property, select the IInvokeLocalService interface, and click OK.

3.   Select the WorkflowCompleted event from the EventName property.

4.   Build the entire solution.

5.   Your updated workflows should now look like Figures 17.14 and 17.15.

FIGURE 17.14 Calling workflow now blocks and waits.

Calling workflow now blocks and waits.

FIGURE 17.15 Called workflow sends notification.

Called workflow sends notification.

Now CallingWorkflow calls CalledWorkflow via InvokeWorkflow activity. Then CallingWorkflow completes synchronous call when it is ready by raising an event on CallingWorkflow via its CallExternalMethod activity.

Implement the Interface

The one major difference with this interface is that it is called from a different workflow than it raises an event on. It is called from the CalledWorkflow and raises an event on the CallingWorkflow. This is why the WorkflowInstanceId was passed as a parameter in the InvokeWorkflow activity when the calling workflow invoked the called workflow. It is this passed instance ID that is now used when raising the event on the calling workflow.

Follow the next steps to create and code the local service.

1.   Add a new class to the InvokeWorkflowProject and name it
InvokeWorkflowLocalService.cs.

2.   Replace the entire class contents with the following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using InvokeWorkflowLocalServices;

namespace InvokeWorkflowProject
{
    class InvokeWorkflowLocalService : IInvokeWorkflowLocalService
      {
        public event EventHandler<CompletedWorkflowEventArgs>
WorkflowCompleted;

        // The called workflow calls this method via a CallExternalMethod
activity.
        public void CommunicateWorkflowStatus(Guid
callingWorkflowInstanceId, string
workflowStatus)
        {
                    // An event is now passed to the calling workflow to signify
completion
                    // the called workflow.
                    CompletedWorkflowEventArgs eventArgs =
                        new CompletedWorkflowEventArgs
                        (callingWorkflowInstanceId, workflowStatus);

                    WorkflowCompleted(null, eventArgs);

        }

      }
}

Registering the ExternalDataExchange Service with the Host

Follow the next steps to register the ExternalDataExchangeService with the host and run the workflow.

1.   Add the following code to Program.cs in the InvokeWorkflowProject at the top of the using block to register the external data exchange service with the host.

                        InvokeWorkflowLocalService iwls = new
InvokeWorkflowLocalService();
                        ExternalDataExchangeService eds = new
ExternalDataExchangeService();
                        workflowRuntime.AddService(eds);
                        eds.AddService(iwls);

2.   Right-click InvokeWorkflowLocalServices, select Resolve, and choose using InvokeWorkflowProject.

3.   Press F5 to run the workflow, and you should receive results similar to Figure 17.16. Both workflows now end nearly simultaneously at the end of processing.

FIGURE 17.16 Invoke workflow activity augmented with CallExternalMethod and HandleExternalEvent activities.

Invoke workflow activity augmented with CallExternalMethod and HandleExternalEvent activities.

Summary

A wide range of topics were covered in this hour. It started with the workflow event and then discussed workflow services. The threading differences between the manual and default scheduling services were shown, and the synchronous nature of the manual scheduler service was pointed out as a potential benefit in some UI scenarios. The InvokeWorkflow activity was covered in fire-and-forget mode and then in a mode in which the host waited for all workflows to complete before expiring. Finally, a synchronous-like experience was created by augmenting the InvokeWorkflow activity with CallExternalMethod and HandleExternalEvent activities. In the same step, parameters were passed from the calling to the called workflow and back.

Workshop

Quiz

1.

What is the purpose of the ManualWorkflowSchedulerService?

2.

What is the purpose of the WorkflowLoaderService?

3.

What is the major limitation of the InvokeWorkflow activity?

4.

What is one of the major items you must provide a solution for when using the InvokeWorkflow activity?

5.

What does the Abort event do?

6.

Can terminated workflows be restarted?

7.

What event should be used to indefinitely stop a workflow from executing that allows it to be restarted?

8.

What does the DefaultWorkflowSchedulerService use to run workflows?

9.

How can a WF host run thousands of workflows if the CLR threadpool on a given computer dedicates 5 or so threads to it?

10.

What is the general purpose for runtime services?

11.

What is the purpose of the WorkflowQueuingService?

Answers

1.

It runs workflows synchronously and is therefore frequently employed when IIS is the host.

2.

The WorkflowRuntime delegates loading the workflow to be executed to it.

3.

It can only call workflows asynchronously.

4.

Keeping the calling workflow blocked while awaiting the response.

5.

It returns the workflow to its last persistence state (like an undo in a word processor).

6.

No. Terminated workflows are unsuccessfully completed.

7.

The Suspend event.

8.

The CLR threadpool.

9.

At any given time, most workflows are persisted and therefore do not require any CLR threadpool threads.

10.

To allow the WF runtime to add additional features necessary for a given application or host.

11.

It is the underlying communication infrastructure for WF. Even when local services and other higher-level abstractions are used, under the hood it is the WorkflowQueuingService that is moving data into and out of WF.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.95.56