WF – Creating an Asynchronous Workflow Activity


What a silly topic for a blog post! Workflow already runs my whole activity tree on a another thread, I don't have to worry about being Async, Do I? Yes, you do. While it is true that each Workflow gets its own thread (in the default setup), that thread can be easily blocked by a long running or hung activity. Let's look at an example...

Create a Sequential Workflow Console application in Visual Studio with the following Activity tree:

Sequential Workflow

    WhileActivity (set the condition to true so it runs forever)

        CodeActivity (the code should just do a Console.WriteLine("Hello World")

So, now go to the Program.cs file or wherever the code is that Starts the workflow instance.

Add a Thead.Sleep(1000) after the instance.Start() call and then an instance.Terminate("kill workflow") to stop the workflow.

When you run the workflow, you should notice that you get a few "Hello World" lines and one "kill workflow" line. Here, terminate works just as you expect it to. But now let's change our workflow so that the while is done in the code activity...

Sequential Workflow

    CodeActivity (the code should contain while(true) Console.WriteLine("Hello World"))

When you run the workflow this time, it will never end 🙁

If you put a break point on the instance.Terminate line, it does get called. So, why doesn't the Workflow terminate? Well, like all other actions that happen in the workflow instance, the terminate action goes through a queue and waits for the background thread to dequeue it and do it. Unfortunately, the queue is only processed in between activities executing. So, the termination request remains in the workflow queue forever in our example.

If you are running complex workflows that may need to actually be stopped in the middle (like we do) then you need to avoid code activities that have loops and start writing asynchronous custom activities. So, how do we write a custom activity so that it is stoppable? The first thing you have to do is create a workflow runtime service that can do the actual work for the activity. This means for every custom activity you need a new custom service or at least a new method on your custom service. Here's a sample service:

    public class HelloWorldService : WorkflowRuntimeService
    {
        public void WriteHelloWorldForeverAsync()
        {
            ThreadPool.QueueUserWorkItem(delegate(Object state)
                {
                    while (true) Console.WriteLine("Hello World");
                });
        }
    }

This service will now do the work for us. But to use it, we need a custom activity. Here's the simplest custom activity you could make:

    public class WriteHelloWorldActivity : Activity
    {
        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            HelloWorldService service = (HelloWorldService)executionContext.GetService(typeof(HelloWorldService));
            service.WriteHelloWorldForeverAsync();
            return ActivityExecutionStatus.Executing;
        }
    }

The key thing to note here is that the activity calls a method on the service that returns immediately after starting its background thread. The activity then returns Executing. This allows the workflow to dequeue items from the queue, but not move on to the next activity. Until this activity returns Closed, the next activity will not start.

Oops! before you try and run this you need to create your service, add it to the runtime and add your custom activity to the workflow. In Program.cs, add the following line right after you create the runtime service:

workflowRuntime.AddService(new HelloWorldService());

Then change the workflow to look like this:

Sequential Workflow

    WriteHelloWorldActivity

Now, you can run it and see that Terminate works again! Of course, in a real application you need more code than this, because you have to shut down the background thread when you get terminated, and you need to call back to your activity when the background work is finished! Tune in for my next post, where I will show you a real example of of an activity and service that do it all 🙂

Skip to main content