Composing Windows Workflow Foundation with .NET Framework Task Parallel Library

At first glance Windows Workflow (WF) and .NET Task Parallel Library (TPL) may appear to be competing execution engines. Both emphasize composing work and parallel execution. While the tools share some characteristics, each tool is very different. TPL is a lower-level library for managing class level workloads and WF is architected around Activity building blocks with User Interface design features as well as work composition. In fact, had TPL been available when WF was built the WF team could have seriously considered building WF on top of TPL.

Even though WF was not built on TPL, there are TPL features that can make running WF workflows easier. In self-hosted scenarios, for example, a desktop application may be running a workflow. Code examples in this article will demonstrate how TPL and WF can work together.

Why a Workflow and a Task?

WF was built for a variety of hosts. So, a developer is not confined to a Server Host like, for example, AppFabric to run WF workflows. The WF Windows Presentation Foundation (WPF) based design experience means a developer can build a design experience around a customizable feature inside, for example, a desktop application.

Because WF includes a design experience as well as customizable Activities; developers requiring application user customization find that workflows are ideal. For example: an application that must perform custom printing could include a set of Printing Activities. Application users could mix and match the set of Print activities depending on the need.

Starting with .NET 4.0 Tasks underpin the Concurrency and Asynchronous .NET Framework unit work standard. Tasks enable capabilities like Continuations. Tasks can loosely couple the result of a Task completion to some other components in an application. For example, in the printing scenario above; a Task can tie a User Interface update to a completed workflow.

As stated earlier, outside of using a server like AppFabric; WF includes a number of options for running a workflow.

Self-Hosting WF

Unless a developer is self-hosting a Workflow Services workflow; there are two techniques for invoking a workflow inside an application.

WorkflowInvoker is the simplest technique. Code utilizing the WorkflowInvoker appears below.

WorkflowInvoker.Invoke(new Workflow1());

The technique runs the workflow synchronously. Invoke exits when the workflow completes. WorkflowInvoker is great for unit testing a workflow. Most self-hosted applications will need more control over an executing workflow. For example: most applications would probably require some sort of workflow abort. A more powerful approach involves the WorkflowApplication class.

WorkflowApplication

WorkflowApplication gives a developer almost complete control over a running workflow. A developer can hook events like workflow exceptions and completions. Workflows can be aborted and workflow exceptions can be handled gracefully.

The sample code below leverages the WorkflowApplication class along with a number of other classes.

enum WorkflowExecResult
{
    Success,
    Failure
}

sealed class WorkflowApplicationWrapper
{
    private WorkflowApplication _instance = null;
    private TaskCompletionSource _task = null;
    private WorkflowExecResult _result = WorkflowExecResult.Failure;
    private Activity _definition = null;

    public WorkflowApplicationWrapper(string fileName)
    {
        using (var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
        {
            _definition = ActivityXamlServices.Load(fileStream);
        }

        _task = new TaskCompletionSource();
    }

    public void Run(CancellationToken token)
    {
        try
        {
            //create a new instance from this definition
            _instance = new WorkflowApplication(_definition);

            _instance.Aborted += this.OnAborted;
            _instance.Completed += this.OnCompleted;
            _instance.OnUnhandledException += this.OnException;

            token.Register(this.AbortWorkflow);

            _instance.Run();

        }
        catch (Exception ex)
        {
            Console.WriteLine("Workflow execution failed " + ex.Message);
            _result = WorkflowExecResult.Failure;
        }
    }

    private void AbortWorkflow()
    {
_instance.Abort("Workflow aborted!!");
    }


    public Task WorkflowTask
    {
        get { return _task.Task; }
    }

    private void OnAborted(WorkflowApplicationAbortedEventArgs args)
    {

    }

    private void OnCompleted(WorkflowApplicationCompletedEventArgs args)
    {
        _result = WorkflowExecResult.Success;

        _task.SetResult(_result);
    }

    private UnhandledExceptionAction OnException(WorkflowApplicationUnhandledExceptionEventArgs args)
    {
        _task.SetResult(_result);

        return UnhandledExceptionAction.Abort;
    }
}

In the code above; the interface to the WorkflowApplication has been wrapped in a helper class. Code using the helper class appears below.

            WorkflowApplicationWrapper wf = null;

            for (var n = 0; n < 3; ++n)
            {
                wf = new WorkflowApplicationWrapper("Workflow1.xaml");
                var tokenSource = new CancellationTokenSource();

                wf.Run(tokenSource.Token);

                if (n == 2) { tokenSource.Cancel(); }
                else { wf.WorkflowTask.Wait(); }

                Console.WriteLine("Workflow completed " + wf.WorkflowTask.Result.ToString());

            }

WF workflows are Trees of Activity classes. WorkflowApplication requires a reference to the root of the workflow Tree.

Workflows can be compiled and included in a .NET assembly or the workflow can be loaded directly from an XML file. Sticking with the Printing scenario introduced earlier in the article, an Application user-generated workflow would likely be saved in an XML format. ActivityXamlServices includes methods to parse and load the workflow.

The WF runtime executes the workflow in a separate Thread.

Fundamental TPL classes called TaskCompletionSource and the CancellationToken give a developer control over a Task class Result property.

TPL Classes

Using TaskCompletionSource and the appropriate WorkflowApplication Event the sample code populates the Task.Result with a value. A complete review of TaskCompletionSource is beyond the scope of this article, but there are helpful resources at the end of the article.

The sample code demonstrates Waiting on a Task. However, making a Task available to a developer gives a class consumer a range of composition options. For example: Task.WaitAll could wait for a group of executing workflows to complete and, as mentioned earlier, a Continuation can update a component on the User Interface.

Aborting a workflow is handled by a CancellationToken class. When the CancellationTokenSource.Cancel is called a delegate registered with the token aborts the workflow instance.

Conclusion

The Task class can improve self-hosted workflow execution. Tasks can be manipulated with the TaskCompletionSource and CancellationToken. Making a Task available to a component consumer gives a developer a range of composition options.

Resources

"Windows Workflow Foundation 4 from the Inside Out"

"The Nature of TaskCompletionSource"

"Using WorkflowInvoker and WorkflowApplication"

"Tasks and the APM Pattern"

"Understanding .NET Framework Task Parallel Library Cancellations"



About the Author

Jeffrey Juday

Jeff is a software developer specializing in enterprise application integration solutions utilizing BizTalk, SharePoint, WCF, WF, and SQL Server. Jeff has been developing software with Microsoft tools for more than 15 years in a variety of industries including: military, manufacturing, financial services, management consulting, and computer security. Jeff is a Microsoft BizTalk MVP. Jeff spends his spare time with his wife Sherrill and daughter Alexandra.

Related Articles

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds