Cannot Invoke Workflows From Workflows Hosted in Asp.Net with Manual Scheduler

WasherAs you can see I've included my famous rusty washer on this post to denote a problem.  I was having a conversation with a team member about Workflow Foundation who was having a problem.  The problem was "Hey Keith, have you ever had workflows in Workflow Foundation invoke workflows?".  Answer:  I  have not, but let's talk about it, what's the problem?

 My team member started explaining that they have a need for workflows to call other workflows.  Sounds simple enough since their is a pre-packaged activity in WF to do just this in the toolbox.

He further elaborated that when they invoke the workflows the workflow runtime dies.  At first I was perplexed, but then knowing they were hosting it in Asp.Net web service I asked which scheduler they were running.  He replied ManualWorkflowSchedulerService.  Ah ha!

The problem is a catch 22 and honestly I don't have a solution for this.  The problem is that when hosting a workflow with the ManualWorkflowSchedulerService it cannot invoke other workflows.  I guess it makes sense since it cannot spawn a new thread, after all it is trying to process a new instance of a workflow on the same thread. (at least that is my theory) Is there a work around?  I don't know.  Is it a problem, yeah!

To compound matters the only way to reliably host workflow foundation in Asp.Net service is with the ManualWorkflowSchedulerService.  Well, ok it isn't the only way, but if there are problems using the default scheduler in Asp.Net.  Here is Paul Andrew's post about a fix coming in WF where he explains the differences.

The DefaultWorkflowSchedulerService is the out-of-box Windows Workflow Foundation scheduler used by the runtime to execute workflow instances on new CPU threads.  These threads are leveraged from the.NET thread pool associated with the host application. This works well unless the workflow runtime is hosted in ASP.NET.  Because of the limited thread resource in the IIS server, we would be allocating an additional unnecessary thread to execute the workflow instance for every HTTP request that required workflow execution. The ManualWorkflowSchedulerService was developed to address this issue by allowing the thread that is processing the HTTP request to execute a workflow instance. Unfortunately in WF Beta 2.2 and prior, it didn’t handle the correct processing of timer events (i.e. delay activities).

When doing Asp.Net Web Services the only reliable way to return output parameters, catch exceptions, logging, handle faults, etc is to run the workflow on the same thread synchronously using the ManualWorkflowSchedulerService.  I've tried every which way I knew to get the default scheduler to work but couldn't.  If you think about it, it makes sense since web services receive a message, then typically return a message.  With the default scheduler it is hard to bubble up exceptions properly so they can be logged or caught and handle gracefully in a service.

To test this theory I created a console workflow app and created two workflows with one calling the other like this.

image

I then wired up the ManualWorkflowSchedulerService in the Program.cs like this and run it.  It doesn't run. If I comment out the manual scheduler and use the default, it works fine.

static void Main(string[] args)

        {

            try

            {

                using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())

                {

                    ManualWorkflowSchedulerService service = new ManualWorkflowSchedulerService(false);

                    workflowRuntime.AddService(service);

                    AutoResetEvent waitHandle = new AutoResetEvent(false);

                    workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e) { waitHandle.Set(); };

                    workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)

                    {

                        Console.WriteLine(e.Exception.Message);

                        waitHandle.Set();

                    };

 

                    WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(WorkflowConsoleApplication1.Workflow1));

                    ManualWorkflowSchedulerService scheduler = workflowRuntime.GetService<ManualWorkflowSchedulerService>();

                    instance.Start();

                    scheduler.RunWorkflow(instance.InstanceId);

                    waitHandle.WaitOne();

                }

            }

            catch (Exception ex)

            {

                Console.WriteLine(ex.Message);

                Console.ReadLine();

            }

 

 So the catch 22 is I need to use the manual scheduler with Asp.Net Web Services but I can't invoke other workflows if I do.  If I use the default scheduler, I can't scale my service.  The "solution" we are going with right now for the work around is to host the service in WCF as a Windows Service outside of IIS.    The bad part of this is you can't really scale this either.  That's the nice thing about using IIS is it really is an app server.  Clustering and those things are widely available.  If you build your own windows service you lose all of that baked in functionality.  I think this is a HUGE rusty washer.   Anyone faced this challenge?  How did you solve it?  Am I off my rocker on this one?  Feedback would be appreciated.

UPDATE (8.28.2007)

One of the developers at work (Joe Brach) solved this problem since he was running into the same issue.  I thought I'd post his example of his fix since several people have been emailing me if I found a fix.  The "fix" is you create your own class that extends WorkflowRuntimeService.  Here is Joe's fix:

    public class StartWorkFlowService: WorkflowRuntimeService

    {

        public Guid StartWorkflow(Type workflowType, Dictionary<string, object> inparms

        {

            //Get an instance of the runtime

            WorkflowRuntime wr = this.Runtime;

 

            //Create the type of Workflow

            WorkflowInstance wi = wr.CreateWorkflow(workflowType, inparms);

 

            //Start the WorkFlow

            wi.Start();

 

            //Get the Manual Workflow Service

            ManualWorkflowSchedulerService ss = wr.GetService<ManualWorkflowSchedulerService>();

 

            if (ss != null)

            {

                ss.RunWorkflow(wi.InstanceId);

            }

            return wi.InstanceId;

        }

    }

Disc Golfing in Michigan - Cass Benton

image Last week I was in Livonia, MI so I could be onsite for work during the week.  On Sunday prior to starting the hectic work week Brad (aka AtomicInternet), Lisa, Ellen and I went to Cass Benton for a round of Disc Golf.  Since our local disc golf course got destroyed during the Katrina hurricane it was the first time we've been since moving back to Mississippi.  Brad documented the golf outing via his blog post.  I detected a hint of Civil War rivalry in his post but maybe that was just me.  In the end I ruled the day with my Cyclone driver.  Hole #3 there is even called "Cyclone" and that is about the only disc I can get the turn I need. 

For the record I didn't have to shoot B-Rad's disc, he threw it into the woods and ultimately into the street for me. 

You can view the pictures from the outing here:

http://atomicinternet.homeip.net/photos/album.asp?album=DISC_GOLF_07-2007

«July»
SunMonTueWedThuFriSat
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234