locked
Should I be able to persist within a PickBranch? RRS feed

  • Question

  • Hi,

    I'm having difficulties with bookmarking and persisting within a PickBranch, and I'm wondering what I'm doing wrong.  Should it be possible?

    In a nutshell, I want a timeout on an operation, so I have a Pick with two PickBranches - one for a Delay, and one for the long-running operation. The long-running operation creates a bookmark so it can be resumed.

    The workflow has previously been bookmarked and persisted.  If I use the same object reference for both bookmark resumptions, it all works.  If I use a newly loaded instance, it fails because it goes to the first bookmark.  It seems to know nothing about the second bookmark at all.

    I accept that it's probably quite hard to see what I mean, so I've put together a simple project that demonstrates the problem.

    Flowchart1.xaml:
    <p:Activity mc:Ignorable="" x:Class="PickProblem.Flowchart1" xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities/design" xmlns:av="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:p="http://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:p1="clr-namespace:PickProblem;assembly=PickProblem" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:sad="clr-namespace:System.Activities.Debugger;assembly=System.Activities" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
      <p:Flowchart sad:XamlDebuggerXmlReader.FileName="C:\Users\Geoff\Documents\Visual Studio 10\Projects\PickProblem\PickProblem\Flowchart1.xaml">
        <p:Flowchart.StartNode>
          <p:FlowStep x:Name="__ReferenceID0">
            <p:FlowStep.Next>
              <p:FlowStep x:Name="__ReferenceID1">
                <p:FlowStep.Next>
                  <p:FlowStep x:Name="__ReferenceID2">
                    <WorkflowViewStateService.ViewState>
                      <scg:Dictionary x:TypeArguments="x:String, s:Object">
                        <av:Point x:Key="ShapeLocation">200,353</av:Point>
                        <av:Size x:Key="ShapeSize">200,34</av:Size>
                      </scg:Dictionary>
                    </WorkflowViewStateService.ViewState>
                    <p1:Log DisplayName="Log: Done." Text="[&quot;Done.&quot;]" />
                  </p:FlowStep>
                </p:FlowStep.Next>
                <WorkflowViewStateService.ViewState>
                  <scg:Dictionary x:TypeArguments="x:String, s:Object">
                    <av:Point x:Key="ShapeLocation">195,270.02</av:Point>
                    <av:Size x:Key="ShapeSize">210,59.96</av:Size>
                    <av:PointCollection x:Key="ConnectorLocation">300,324.98 300,353</av:PointCollection>
                  </scg:Dictionary>
                </WorkflowViewStateService.ViewState>
                <p:Pick>
                  <p:PickBranch>
                    <p:PickBranch.Trigger>
                      <p:Delay DisplayName="30 Seconds Delay">[TimeSpan.FromSeconds(30)]</p:Delay>
                    </p:PickBranch.Trigger>
                    <p1:Log DisplayName="Log: Timeout." Text="[&quot;Timeout.&quot;]" />
                  </p:PickBranch>
                  <p:PickBranch>
                    <p:PickBranch.Trigger>
                      <p1:Bookmarker DisplayName="Second Bookmark" />
                    </p:PickBranch.Trigger>
                    <p1:Log DisplayName="Log: Bookmark Resumed." Text="[&quot;Bookmark Resumed.&quot;]" />
                  </p:PickBranch>
                </p:Pick>
              </p:FlowStep>
            </p:FlowStep.Next>
            <WorkflowViewStateService.ViewState>
              <scg:Dictionary x:TypeArguments="x:String, s:Object">
                <av:Point x:Key="ShapeLocation">200,193</av:Point>
                <av:Size x:Key="ShapeSize">200,34</av:Size>
                <av:PointCollection x:Key="ConnectorLocation">300,227 300,275.02</av:PointCollection>
              </scg:Dictionary>
            </WorkflowViewStateService.ViewState>
            <p1:Bookmarker DisplayName="First Bookmark" />
          </p:FlowStep>
        </p:Flowchart.StartNode>
        <WorkflowViewStateService.ViewState>
          <scg:Dictionary x:TypeArguments="x:String, s:Object">
            <av:Point x:Key="ShapeLocation">275,10</av:Point>
            <av:Size x:Key="ShapeSize">50,50</av:Size>
            <av:PointCollection x:Key="ConnectorLocation">300,60 300,193</av:PointCollection>
          </scg:Dictionary>
        </WorkflowViewStateService.ViewState>
        <x:Reference>__ReferenceID1</x:Reference>
        <x:Reference>__ReferenceID2</x:Reference>
        <x:Reference>__ReferenceID0</x:Reference>
      </p:Flowchart>
    </p:Activity>
    Bookmarker.cs:
    namespace PickProblem
    {
        using System;
        using System.Activities;
    
        public class Bookmarker : NativeActivity<string>
        {
            protected override void Execute (ActivityExecutionContext context)
            {
                context.CreateNamedBookmark ("Wibble", OnBookmarkCallback);
    
                return;
            }
    
            private void OnBookmarkCallback (ActivityExecutionContext context, Bookmark bookmark, object obj)
            {
                Console.Out.WriteLine("Got text: {0} in {1}.", obj, DisplayName);
    
                Result.Set (context, (string) obj);
    
                return;
            }
        }
    }
    

    Log.cs:
    namespace PickProblem
    {
        using System;
        using System.Activities;
    
        public class Log : CodeActivity
        {
            [IsRequired]
            public InArgument<string> Text
            {
                get;
                set;
            }
    
            protected override void Execute (CodeActivityContext context)
            {
                string messageText = Text.Get (context);
                Console.Out.WriteLine (messageText);
    
                return;
            }
        }
    }
    

    Program.cs:

    namespace PickProblem
    {
        using System;
        using System.ServiceModel.Persistence;
        using System.Threading;
        using System.Activities;
    
        class Program
        {
            static void Main ()
            {
                AutoResetEvent syncEvent = new AutoResetEvent (false);
    
                Guid persistenceId = Guid.NewGuid ();
    
                var factory = new SqlPersistenceProviderFactory (@"data source=.\SQLEXPRESS;Integrated Security=SSPI;Initial Catalog=Workflow", true);
                factory.Open ();
                var provider = factory.CreateProvider (persistenceId);
    
                var originalInstance = new WorkflowInstance (new Flowchart1 ());
                SetupInstance (originalInstance, syncEvent, provider);
    
                originalInstance.Run ();
    
                ResumeBookmarkWithInput (originalInstance);
    
                // Load instance from store
                var otherInstance = WorkflowInstance.Load (new Flowchart1 (), provider);
                SetupInstance (otherInstance, syncEvent, provider);
    
                // This call succeeds as I expect
                //ResumeBookmarkWithInput (originalInstance);
    
                // This call fails, going to the First Bookmark
                ResumeBookmarkWithInput (otherInstance);
    
                syncEvent.WaitOne ();
    
                Console.Out.WriteLine ("Press <Enter> to quit.");
                Console.In.ReadLine ();
    
                return;
            }
    
            private static void SetupInstance (WorkflowInstance myInstance, AutoResetEvent syncEvent, PersistenceProvider provider)
            {
                myInstance.Extensions.Add (provider);
                myInstance.OnCompleted = delegate (WorkflowCompletedEventArgs e) { syncEvent.Set (); };
                myInstance.OnAborted = delegate (WorkflowAbortedEventArgs e)
                                           {
                                               Console.WriteLine (e.Reason);
                                               syncEvent.Set ();
                                           };
    
                myInstance.OnUnhandledException = delegate (WorkflowUnhandledExceptionEventArgs e)
                                                      {
                                                          Console.WriteLine (e.UnhandledException.ToString ());
                                                          return UnhandledExceptionAction.Terminate;
                                                      };
    
                myInstance.OnIdle = () => IdleAction.Persist;
    
                return;
            }
    
            private static void ResumeBookmarkWithInput (WorkflowInstance myInstance)
            {
                Console.Out.Write ("Enter some text: ");
                string result = Console.In.ReadLine ();
                myInstance.ResumeBookmark ("Wibble", result);
    
                return;
            }
        }
    }
    



     

    I can provide the whole project if you like.  When you run it, it asks you for some text and resumes the first bookmark successfully.  Then it asks for some more text and tries to resume the second bookmark.  It fails if the loaded instance is used, and it succeeds if the existing instance is used.

    I get the feeling I'm doing something fundamentally wrong, but I don't know enough about WF4 to know what that is.

    Any ideas?

    Many thanks,

        Geoff
    Friday, September 11, 2009 5:41 PM

Answers

  • Hi Geoff,

    I have done with some debugging on your code, and I believe the problem is that we haven't give delay a way to go idle, and that's why the workflow never go idle and therefore never persist in that PickBranch.

    Internally, the delay activity depends on a TimerExtension to work. The TimerExtension works like an alarm. The delay activity tell the TimerExtension that I need to sleep for 30 seconds, and the timer extension will tell whether the Delay activity can go idle or not.

    In your case, the Delay activity got the ViolatileTimerExtension by default, which uses a in memory timer. This timer do not persist across process recycle, therefore, the system do not allow Delay to go idle. If a DurableTimerExtension is set, then the TimerExtension can save the timer into the persistence store, and therefore the workflow can go idle and persist for process recycle.

    DurableTimerExtension is only available through WorkflowServiceHost in beta1. We will get this fixed in beta2. I am not sure, but you might be able to workaround by implementing your own DurableTimerExtension.

    In short, your scenario (running a standalone workflow instance (not a workflow service) and expect to persist during delay) is not supported in beta1 and will be fixed in beta2.

    Thanks,
    Andrew

    Friday, September 18, 2009 8:15 AM

All replies

  • This should absolutely work. I'll have our test team try to repro on our latest bits.
    Tuesday, September 15, 2009 9:34 PM
  • This should absolutely work. I'll have our test team try to repro on our latest bits.

    Thanks for that - I can email you the complete VS2010 project if it'll help.

    Cheers,

        Geoff
    Wednesday, September 16, 2009 7:44 AM
  • Hi Geoff,

    I have done with some debugging on your code, and I believe the problem is that we haven't give delay a way to go idle, and that's why the workflow never go idle and therefore never persist in that PickBranch.

    Internally, the delay activity depends on a TimerExtension to work. The TimerExtension works like an alarm. The delay activity tell the TimerExtension that I need to sleep for 30 seconds, and the timer extension will tell whether the Delay activity can go idle or not.

    In your case, the Delay activity got the ViolatileTimerExtension by default, which uses a in memory timer. This timer do not persist across process recycle, therefore, the system do not allow Delay to go idle. If a DurableTimerExtension is set, then the TimerExtension can save the timer into the persistence store, and therefore the workflow can go idle and persist for process recycle.

    DurableTimerExtension is only available through WorkflowServiceHost in beta1. We will get this fixed in beta2. I am not sure, but you might be able to workaround by implementing your own DurableTimerExtension.

    In short, your scenario (running a standalone workflow instance (not a workflow service) and expect to persist during delay) is not supported in beta1 and will be fixed in beta2.

    Thanks,
    Andrew

    Friday, September 18, 2009 8:15 AM
  • Hi there,

    Thanks for that. I've tried putting it in a WorkflowServiceHost as that seems to be the 'proper' answer for my project in the long term.  However, doing so has raised a baffling correlation/persistence issue I've raised at:
        http://social.msdn.microsoft.com/Forums/en-US/wfprerelease/thread/fe284256-c830-405d-b863-55a316613111

    I do hope to get it all working together sometime!

    Cheers,
        Geoff
    Thursday, October 8, 2009 2:53 PM