Question

I am trying to achieve the following:

  1. Start a workflow that may go running for a long time which includes custom activities;
  2. These custom activities will follow the pattern of creating a bookmark and waiting for its resume;
  3. Return the started workflow id and store it somewhere;
  4. At a later point in time, load the workflow from its stored id;
  5. Check if the workflow has completed and, if not, check if there are any blocked bookmarks;
  6. Resume these bookmarks, thus causing the related activities to finish, eventually causing the full workflow to finish.

A simple use case would be: a workflow for reviewing a document and either approving it or rejecting it. A workflow would be created for that matter, individuals would be notified, and, whenever they wanted, they could provide their feedback, by approving or rejecting the review.

I used as an example, the code from Andrew Zhu, available at http://xhinker.com/post/WF4.aspx.

The problems I have are:

  1. When using a custom activity like the one I described, upon starting the workflow, the WaitForRunnableInstance waits indefinitely, so I am never notified that my workflow has started and was persisted to the database;
  2. The workflow is indeed stored, and has column BlockingBookmarks set to the id of my custom activity, ExecutionStatus set to Idle, IsInitialized set to 1, IsSuspended set to 0, IsCompleted set to 0 and IsReadyToRun set to 0.

I already started a discussion on Microsoft Forums, that can be seen at http://social.msdn.microsoft.com/Forums/en-US/wfprerelease/thread/6262874d-3493-4be1-bd05-b990307e1875/ and got some feedback, but something is still not right.

Any ideas on this? Any useful patterns for long running workflows with custom activities?

Thanks!

Was it helpful?

Solution

This is usually the smallest example of a long-running workflow, that waits for user input on console. (this code was never executed, take it as an example only)

/// Activity that waits on bookmark for
/// someone to send it some text
///
public sealed class ReadLine: NativeActivity<string>
{
    [RequiredArgument]
    public InArgument<string> BookmarkName { get; set; }

    protected override bool CanInduceIdle 
    {
        get
        {
            return true;
        }
    }

    protected override void Execute(NativeActivityContext context)
    {
        context.CreateBookmark(
            BookmarkName.Get(context), 
            new BookmarkCallback(OnReadComplete));
    }

    void OnReadComplete(NativeActivityContext context, Bookmark bookmark, object state)
    {
        context.SetValue(base.Result, state as string);
    }
}

/// Program that uses ReadLine activity's bookmark to persist
/// workflow and waits for user input to resume it
///
public class Program
{
    static InstanceStore InstanceStore;
    static Activity Activity = GetExampleActivity();
    static AutoResetEvent unloadEvent = new AutoResetEvent(false);
    static Guid WfId;
    static WorkflowApplication WfApp;

    const string READ_LINE_BOOKMARK = "ReadLineBookMark";

    static void Main()
    {
        CreateInstanceStore();
        CreateWorkflowApp();

        // Start workflow application and wait for input
        StartAndUnload();

        //Get user input and send it to ReadLine bookmark reviving workflow
        GetInputAndComplete();
    }

    static void StartAndUnload()
    {
        WfApp.Run();
        WfId = app.Id;
        // !! Workflow will go idle on bookmark, no need to call Unload()
        unloadEvent.WaitOne();
    }

    static void GetInputAndComplete() 
    {
        var input = Console.ReadLine();

        // We've the text input, let's resume this thing
        WfApp.Load(WfId);
        WfApp.ResumeBookmark(READ_LINE_BOOKMARK, input);

        unloadEvent.WaitOne();
    }

    static void CreateInstanceStore()
    {
        InstanceStore = new SqlWorkflowInstanceStore("connection string");

        var handle = InstanceStore.CreateInstanceHandle();            
        var view = InstanceStore.Execute(
            handle, 
            new CreateWorkflowOwnerCommand(), 
            TimeSpan.FromSeconds(5));            

        handle.Free();

        InstanceStore.DefaultInstanceOwner = view.InstanceOwner;
    }

    static void CreateWorkflowApp()
    {
        WfApp = new WorkflowApplication(Activity)
        {
            InstanceStore = InstanceStore,
        };

        WfApp.PersistableIdle = (e) => { return PersistableIdleAction.Unload; }
        WfApp.Unloaded = (e) => 
        { 
            Console.WriteLine("WF App Unloaded\n");
            unloadEvent.Set(); 
        };
        WfApp.Completed = (e) =>
        {
            Console.WriteLine("\nWF App Ended: {0}.", e.CompletionState);
        };
    }

    static Activity GetExampleActivity()
    {
        var response = new Variable<string>();

        return return new Sequence()
        {
            Variables = { response },
            Activities = 
            { 
                new WriteLine()
                { 
                    Text = new InArgument<string>("Type some word:") 
                },
                new ReadLine() 
                { 
                    BookmarkName = READ_LINE_BOOKMARK, 
                    Result = new OutArgument<string>(response)
                },
                new WriteLine()
                {
                    Text = new InArgument<string>((context) => "You've typed: " + response.Get(context))
                }
            }
        };
    }

That being said, please consider using IIS and AppFabric and you won't regret. AppFabric, with half a dozen clicks, takes care of two of the must painful things to implement within WF: persistence and monitoring. You won't never need to write the code bellow if you choose this path.

Deploy your workflow as a WCF application and you just call it as any other WCF contract. You've OperationContracts which are receive activities (those who wait and persist if it takes too long) and the corresponding send activities (those who return the value back to the client). You even have the concept of correlation among them. AppFabric takes care of the workflow resuming, just pass to it a previously initialized correlation handle.

AppFabric gives you a configuration UI to configure Persistence Store, monitoring and other options like how much time before goes idle and/or persist.

You can visualize Active/Idle/Suspended workflows, monitoring data, etc, etc.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top