views:

147

answers:

2

I have the following code which lets me execute a workflow. This could be called repeatedly. And often is. It's also living in a webservice, so there could be multiple calls to it at the same time. This currently works. But it's slow, since instantiating a WorkflowRuntime each time is very slow.

How can I improve this?

public class ApprovalWorkflowRunner : IApprovalWorkflowRunner
{
    private static ILogger Logger { get; set; }
    private static IRepository Repository { get; set; }

    public ApprovalWorkflowRunner(ILogger logger, IRepository repository)
    {
        Logger = logger;
        Repository = repository;
    }

    public Request Execute(Action action)
    {
        var request = new Request();

        using (var workflowRuntime = new WorkflowRuntime())
        {
            workflowRuntime.StartRuntime();
            var waitHandle = new AutoResetEvent(false);
            workflowRuntime.WorkflowCompleted += ((sender, e) =>
                                                    {
                                                        waitHandle.Set();
                                                        request = e.OutputParameters["gRequest"] as Request;
                                                    });
            workflowRuntime.WorkflowTerminated += ((sender, e) =>
                                                    {
                                                        waitHandle.Set();
                                                        Logger.LogError(e.Exception, true, action.Serialize());
                                                    });

            var parameters = new Dictionary<string, object>
                                {
                                    {"RepositoryInstance", Repository},
                                    {"RequestID", action.RequestID.ToString()},
                                    {"ActionCode", action.ToString()}
                                };

            var instance = workflowRuntime.CreateWorkflow(typeof (ApprovalFlow), parameters);
            instance.Start();
            waitHandle.WaitOne();
        }

        return request;
    }
}

Ideally, I'd like to keep one copy of the WorkflowRuntime around. But since I'm passing other objects around in the CreateWorkflow function and WorkflowCompleted event, I don't see how it would work.

...am I missing something simple here, there's a good chance my brain didn't tell my body it wasn't showing up to work today.

A: 

One runtime can run many workflows at the same time. As per the answer here:

That page shows some code for a WorkflowRuntime factory that I will include below, that I believe was originally taken from the Windows Workflow Foundation Step by Step Book

public static class WorkflowFactory
{
    // Singleton instance of the workflow runtime
    private static WorkflowRuntime _workflowRuntime = null;

    // Lock (sync) object
    private static object _syncRoot = new object();

    /// <summary>
    /// Factory method
    /// </summary>
    /// <returns></returns>
    public static WorkflowRuntime GetWorkflowRuntime()
    {
        // Lock execution thread in case of multi-threaded
        // (concurrent) access.
        lock (_syncRoot)
        {
            // Check for startup condition
            if (null == _workflowRuntime)
            {
                // Provide for shutdown
                AppDomain.CurrentDomain.ProcessExit += new EventHandler(StopWorkflowRuntime);
                AppDomain.CurrentDomain.DomainUnload += new EventHandler(StopWorkflowRuntime);

                // Not started, so create instance
                _workflowRuntime = new WorkflowRuntime();

                // Start the runtime
                _workflowRuntime.StartRuntime();
            } // if

            // Return singleton instance
            return _workflowRuntime;
        } // lock
    }

    // Shutdown method
    static void StopWorkflowRuntime(object sender, EventArgs e)
    {
        if (_workflowRuntime != null)
        {
            if (_workflowRuntime.IsStarted)
            {
                try
                {
                    // Stop the runtime
                    _workflowRuntime.StopRuntime();
                }
                catch (ObjectDisposedException)
                {
                    // Already disposed of, so ignore...
                } // catch
            } // if
        } // if
    }
}

You would simply call

WorkflowFactory.GetWorkflowRuntime();

EDIT: OK sorry. You can try checking the instance is the one you expect, and returning if it's not. Please note this code is untested, just trying to get the idea across.

public class ApprovalWorkflowRunner : IApprovalWorkflowRunner
{
    private static ILogger Logger { get; set; }
    private static IRepository Repository { get; set; }

    public ApprovalWorkflowRunner(ILogger logger, IRepository repository)
    {
        Logger = logger;
        Repository = repository;
    }

    public Request Execute(Action action)
    {
        var request = new Request();

        var workflowRuntime = WorkflowFactory.GetWorkflowRuntime();

        workflowRuntime.StartRuntime();
        var waitHandle = new AutoResetEvent(false);
        WorkflowInstance instance = null;
        workflowRuntime.WorkflowCompleted += ((sender, e) =>
                                                {
                                                    if (e.WorkflowInstance != instance) return;
                                                    waitHandle.Set();
                                                    request = e.OutputParameters["gRequest"] as Request;
                                                });
        workflowRuntime.WorkflowTerminated += ((sender, e) =>
                                                {
                                                    if (e.WorkflowInstance != instance) return;
                                                    waitHandle.Set();
                                                    Logger.LogError(e.Exception, true, action.Serialize());
                                                });

        var parameters = new Dictionary<string, object>
                            {
                                {"RepositoryInstance", Repository},
                                {"RequestID", action.RequestID.ToString()},
                                {"ActionCode", action.ToString()}
                            };

        instance = workflowRuntime.CreateWorkflow(typeof (ApprovalFlow), parameters);
        instance.Start();
        waitHandle.WaitOne();

        return request;
    }
}
Luke Schafer
@Luke Schafer, the issue I have is that I'm passing parameters in and waiting for output. Will that still work with `WorkflowFactory.GetWorkflowRuntime();` when multiple of them get called at once by different users? Or will things get all mixed up?
Chad
Edit included above
Luke Schafer
A: 

I would make a couple of observations about the code you are currently using. In doing anything to optimize your code you should keep firmly in mind a target for when your code is efficient enough, as code optimization tends to follow the law of diminishing returns in that code optimization requires more and more effort.

The most simplest thing I can think of to increase the codes speed is to set the ValidateOnCreate property in the WorkflowRuntime instance to false. By default it is set to true which means that every time you create your workflow instance. I presume that your workflow is static in that you are not making dynamic changes to it at runtime, but rather have defined it at compile time. If this is the case you should be able to skip the validation step. Depending on how complicated your workflow is this may significantly improve the speed of the code.

Presuming that this does not increase the speed enough the other suggestion is the following. Make the WaitHandle, Request and Workflow objects used in the Execute method instance members of the ApprovalWorkflowRunner class. The WorkflowRuntime would be a parameter to the Execute method. In doing this you should then create a new instance of the ApprovalWorkflowRunner class every time you want to run the ApprovalFlow workflow. Your Execute method should then look something like this:

    public Request Execute(Action action, WorkflowRuntime workflowRuntime) {
            workflowRuntime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(workflowRuntime_WorkflowCompleted);
            workflowRuntime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(workflowRuntime_WorkflowTerminated);

            var parameters = new Dictionary<string, object>
                        {
                            {"RepositoryInstance", Repository},
                            {"RequestID", action.RequestID.ToString()},
                            {"ActionCode", action.ToString()}
                        };

            mWorkflowInstance = workflowRuntime.CreateWorkflow(typeof(ApprovalFlow), parameters);
            mWorkflowInstance.Start();
            mWaitHandle.WaitOne();

        return mRequest;
    }

The event handlers inside of the ApprovalWorkflowRunner class would look something like this:

    void workflowRuntime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e) {
        if (!e.WorkflowInstance.Equals(mWorkflowInstance)) return;
        mRequest = e.OutputParameters["gRequest"] as Request;
        mWaitHandle.Set();
        return;
    }

Note that I have switched the two lines inside the handler from the way you had them in your code, setting the wait handle before you assign the request instance creates a race condition.

On a final note: the invoking of the Dispose method on the WorkflowRuntime instance would obviously have to take place elsewhere in your code; however Microsoft recommends calling the StopRuntime method prior to calling Dispose: Remarks: shut down the WorkflowRuntime gracefully

Hope this helps

Steve Ellinger