Workflow Faults


Faults in Windows Workflow Foundation are a lot like exceptions in .NET. They differ in one major respect, however. In .NET, when an exception is thrown, it immediately starts looking for the closest relevant catch block up the stack. When a fault (exception) occurs in Windows Workflow Foundation, it is thrown to the workflow runtime, but the workflow is not immediately aborted. Instead, the exception is added to a special queue, which is handled at the appropriate time.

Workflow faults can occur for any number of reasons, such as issues in activity execution or with one of the runtime services. If any exceptions get raised from standard .NET code, they also get raised as workflow faults. Workflows can fail in just the same way that .NET code can, and you need to account for these failures with the techniques described in this section.

To illustrate how faults work in Windows Workflow Foundation, this section uses a simple example workflow. This workflow has a Parallel activity that contains two branches. The first branch prints a message, waits a specified time, and then prints another message. The second parallel branch prints a message, throws an exception, and then prints another message. The second message in the second branch never actually prints because the exception is being thrown. Figure 12-1 shows this workflow.

image from book
Figure 12-1

The following is some of the code from the workflow’s host (not including the standard plumbing code, which was removed for brevity):

  public class Program {     ...     public static void Main(string[] args)     {         ...         runtime.WorkflowTerminated +=             new EventHandler<WorkflowTerminatedEventArgs>(                 runtime_WorkflowTerminated);         ...     }     public static void runtime_WorkflowTerminated(object sender,         WorkflowTerminatedEventArgs e)     {         Console.WriteLine("The workflow (" +             e.WorkflowInstance.InstanceId.ToString() + ") was terminated!");         waitHandle.Set();     } } 

You can see that the WorkflowRuntime’s WorkflowTerminated event has been subscribed to. This is the event that is raised when an unhandled exception, or fault, is raised all the way up to the workflow runtime. Subscribing to this event and printing a message in the runtime host can help you figure out what’s going on related to fault handling in the workflow instance and workflow runtime.

If you were to run the program in its current state, you would receive a series of messages similar to the console window in Figure 12-2.

image from book
Figure 12-2

By reviewing the messages that are printed from the workflow and the workflow host, you can start to decipher what happened:

  1. The left branch executed its first CodeActivity.

  2. The right branch executed its first CodeActivity.

  3. The left branch executed its DelayActivity.

  4. The right branch executed the ThrowActivity.

At this point, the fault gets thrown all the way up to the workflow runtime, and the workflow is terminated. Because a fault was raised in the middle of the Parallel activity’s execution, the remainder of its work was canceled. The same goes for the workflow itself. If there were more work to do after the Parallel activity, that work would never be started or completed. This seems pretty sloppy, right? In its current state, the workflow doesn’t even have a chance to gracefully handle the fault or any of its consequences. However, Windows Workflow Foundation provides the infrastructure to do just this.

Handling Faults

To better manage the exception thrown in the example workflow, you could do one of a few different things. In all methods, the FaultHandlers and FaultHandler activities are part of the solution. The FaultHandlers activity is a container for zero or more FaultHandler activity instances.

You cannot directly add a FaultHandlers activity to your workflow, mainly because this activity is used only in certain instances, and the workflow designer takes care of doing this for you. Each composite activity in a workflow, including the workflow itself, has an associated FaultHandlers activity to manage faults raised while that activity and its children are executing. To access an activity’s FaultHandlers activity, you need to access the activity’s context menu and select the View Fault Handlers option. The workflow designer has a set of icons in the bottom-left corner that provide easy access to the workflow’s fault handlers. To access a sequential workflow’s fault handlers, click the View Fault Handlers button in the bottom-left corner of the workflow designer (see Figure 12-3). To access the IfElseBranch activity’s fault handlers, click the View Fault Handlers button on the activity’s view menu (see Figure 12-4).

image from book
Figure 12-3

image from book
Figure 12-4

Remember the sample C# exception handlers shown earlier in this chapter? The FaultHandlers activity works like an entire group of catch blocks, whereas the FaultHandler activity is like an individual catch block. Given this comparison, it’s not hard to understand why each FaultHandler activity instance has a particular exception type associated with it, which is done through the FaultType property. In addition, if a particular fault handler is executed because a certain exception type was raised, the exception instance can be captured and bound to a member of the workflow class. You do this by right-clicking a specific FaultHandler activity and selecting the Promote Bindable Properties option from the context menu. This creates a new dependency property in the workflow code-beside class and binds it to the fault handler’s Fault property. Again, the FaultHandlers activity is like the set of catch blocks in C# because its child handler activities go from most to least specific.

Figure 12-5 shows what the FaultHandlers and FaultHandler activity instances look like in the workflow designer. You can see that a fault handler can contain child activities that are executed whenever the FaultHandler activity is executed. Also, some of a FaultHandler activity’s properties are displayed on the right side of the screen. The selected activity is responsible for handling errors related to a SQL database.

image from book
Figure 12-5

Now back to the example workflow with the parallel tasks. An appropriate fault handler needs to be placed somewhere in the workflow. Where you should place this fault handler depends on the application and what kind of error has occurred. In this example, a fault handler could be placed in the second parallel branch, in the Parallel activity itself, or on the workflow. In any event, the fault is raised up starting at the immediate parent until it finds a suitable handler. For example, if you place a FaultHandler activity on the example workflow and then rerun the program, the output would look like Figure 12-6.

image from book
Figure 12-6

Now the workflow doesn’t get terminated because the fault never had a chance to the leave the workflow. However, there is probably still some work to be done before the workflow really handles any issues gracefully. Think about the first parallel branch in this scenario. If the second branch is careless and causes an issue, the first branch is punished by prematurely being canceled. Although this is a fact of life, the first branch should at least be given a chance to perform some work before it is abruptly stopped. That is where cancellation handlers come in.

Cancellation Handlers

Just like fault handlers, cancellation handlers are located in composite activities, including the workflow itself. You can also access cancellation handlers in the same manner as fault handlers in the workflow designer. Cancellation handlers allow sibling activities in a composite parent to gracefully stop execution when another sibling has caused a fault. This is common when dealing with the Parallel activity, as in the example discussed in this chapter. However, any composite activity with multiple branches can take advantage of the cancellation handler infrastructure.

You implement cancellation handlers using the CancellationHandlerActivity class, which inherits from CompositeActivity. In the example with the Parallel activity, you could use the cancellation handler of the first parallel branch so that it has a chance to do something before it is killed off prematurely. If a Code activity that printed a message were added to the first branch’s cancellation handler, the program would finally produce some output, as shown in Figure 12-7.

image from book
Figure 12-7

Throwing Faults

Just as it is important to be able to catch and handle exceptions that occur during workflow execution, it is equally important to be able to raise exceptions if something goes wrong. There are a couple of different ways to do this.

The traditional method is to throw .NET exception instances from .NET code. To do this, you could place the following code within the Code activity’s ExecuteCode event handler:

  private void myCodeActivity_ExecuteCode(object sender, EventArgs e) {    if (someInteger < 0)       throw new ApplicationException("The value cannot be negative.");    // perform some other operations... } 

This code simply throws a new ApplicationException instance when an int variable is not greater than zero. This causes the workflow runtime to handle the exception and create a new workflow fault.

Another method is to use the Throw activity to raise faults declaratively. This is not much different from throwing an exception from a block of code. It does, however, contribute to the declarative nature of workflows.

The Throw activity exposes the Fault and FaultType properties, which are both dependency properties. You can set these properties independently of each other, but you must set at least one of them to correctly configure the activity.

If you set FaultType to a .NET exception type, when the Throw activity executes, it throws a new instance of that exception type given its default constructor. Conversely, if you set the Fault property to point to an exception instance somewhere in the workflow, that instance is thrown when the Throw activity is executed. If you set these two properties at the same time, the FaultType property makes sure that the exception instance set using the Fault property is of the same type. If you try to set the Fault property to an exception instance that is not the same type as the one specified in the FaultType property, the activity does not pass validation.

The following is code from a workflow host application that subscribes to the WorkflowRuntime’s Terminated event:

  workflowRuntime.WorkflowTerminated +=     delegate(object sender, WorkflowTerminatedEventArgs e)     {         Console.WriteLine("Exception of type \"" +             e.Exception.GetType().FullName + "\" was thrown: " +             e.Exception.Message);         waitHandle.Set();     }; 

Because any exceptions that are unhandled in the workflow get thrown to the runtime, which then terminates the workflow, this event-handler code is called when any unhandled exceptions are raised.

Figure 12-8 shows the message that is printed when a Throw activity’s FaultType and Fault properties are set. The FaultType property is set to System.ApplicationException, and the Fault property is bound to an ApplicationException instance in the workflow’s code-beside class.

image from book
Figure 12-8



Professional Windows Workflow Foundation
Professional Windows Workflow Foundation
ISBN: 0470053860
EAN: 2147483647
Year: 2004
Pages: 118
Authors: Todd Kitta

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