Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Build Activity WaitForSignalOrDelay through Workflow

4.86/5 (4 votes)
8 Apr 2016CPOL2 min read 11.1K  
Wait for signal or delay

Introduction

Presumably you have read these articles or similar tutorials:

In addition to crafting codes, you can also develop new activity through declarative programming with Workflow/XAML.

Using the Code

The source code is available at https://github.com/zijianhuang/WorkflowDemo.

Prerequisites:

  1. Visual Studio 2015 Update 1 or Visual Studio 2013 Update 3
  2. xUnit (included)
  3. EssentialDiagnostics (included)
  4. Workflow Persistence SQL database, with default local database WF.

Examples in this article are from a test class: WorkflowApplicationTests.

WaitForSignalOrDelay

Activity WaitForSignalOrDelay can wait for an incoming bookmark call or a duration of delay.

Wakeup

C#
public sealed class Wakeup : NativeActivity
{
    public Wakeup()
    {
    }

    public InArgument<string> BookmarkName { get; set; }

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

    protected override void Execute(NativeActivityContext context)
    {
        string name = this.BookmarkName.Get(context);

        if (name == null)
        {
            throw new ArgumentException(string.Format("ReadLine {0}: BookmarkName cannot be null", this.DisplayName), "BookmarkName");
        }

        context.CreateBookmark(name);
    }

}

This is a simplest bookmark activity without needing bookmark value and return.

Compose through XAML

So you just need 2 blocking activities: Wakeup and Delay running in parallel through Pick. Either activity gets waked up, the Sequence will go through.

Image 1

Image 2

To make the workflow available as a component, 2 InArguments are defined. And the Result is true for wakeup call, and false for duration expiration.

Tests

C#
[Fact]
public void TestWaitForSignalOrDelayWithBookmarkToWakup()
{
    var bookmarkName = "Wakup Now";
    var a = new WaitForSignalOrDelay()
    {
        Duration = TimeSpan.FromSeconds(10),
        BookmarkName = bookmarkName,
    };

    AutoResetEvent syncEvent = new AutoResetEvent(false);

    bool completed1 = false;
    bool unloaded1 = false;
    IDictionary<string, object> outputs = null;
    var app = new WorkflowApplication(a);
    app.InstanceStore = WFDefinitionStore.Instance.Store;
    app.PersistableIdle = (eventArgs) =>
    {
        return PersistableIdleAction.Unload;
    };

    app.OnUnhandledException = (e) =>
    {

        return UnhandledExceptionAction.Abort;
    };

    app.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
    {
        completed1 = true;
        outputs = e.Outputs;
        syncEvent.Set();
    };

    app.Aborted = (eventArgs) =>
    {

    };

    app.Unloaded = (eventArgs) =>
    {
        unloaded1 = true;
        syncEvent.Set();
    };

    var id = app.Id;
    app.Run();
    syncEvent.WaitOne();

    Assert.False(completed1);
    Assert.True(unloaded1);

    outputs = LoadWithBookmarkAndComplete(a, id, bookmarkName, null);//Wakup does not need bookmark value
    Assert.True((bool)outputs["Result"]);
}

[Fact]
public void TestWaitForSignalOrDelayAndWakupAfterPendingTimeExpire()
{
    var a = new WaitForSignalOrDelay()
    {
        Duration=TimeSpan.FromSeconds(10),
        BookmarkName="Wakeup",
    };

    AutoResetEvent syncEvent = new AutoResetEvent(false);

    bool completed1 = false;
    bool unloaded1 = false;
    var app = new WorkflowApplication(a);
    app.InstanceStore = WFDefinitionStore.Instance.Store;
    app.PersistableIdle = (eventArgs) =>
    {
        return PersistableIdleAction.Unload;
    };

    app.OnUnhandledException = (e) =>
    {
        //
        return UnhandledExceptionAction.Abort;
    };

    app.Completed = delegate (WorkflowApplicationCompletedEventArgs e)
    {
        completed1 = true;
        syncEvent.Set();
    };

    app.Aborted = (eventArgs) =>
    {
        //
    };

    app.Unloaded = (eventArgs) =>
    {
        unloaded1 = true;
        syncEvent.Set();
    };

    var id = app.Id;
    app.Run();

    syncEvent.WaitOne();
    Assert.False(completed1);
    Assert.True(unloaded1);

    WFDefinitionStore.Instance.TryAdd(id, a);

    Thread.Sleep(5000); // from 1 seconds to 9 seconds, the total time of the test case is the same.

    var outputs = LoadAndCompleteLongRunning(id);

    Assert.False((bool)outputs["Result"]);
}

Use the WaitForSignalOrDelay activity/workflow in another workflow

Image 3

WaitForSignalOrAlarm

This class is very similar to the WaitForSignalOrDelay, and is crafted through C# coding. And the activity will continue upon a specific DateTime, rather than after a duration.

C#
public sealed class WaitForSignalOrAlarm : Activity<bool>
{
    public InArgument<DateTime> AlarmTime { get; set; }

    public InArgument<string> BookmarkName { get; set; }

    public WaitForSignalOrAlarm()
    {
        Implementation = () =>
            new Pick()
            {
                Branches = {
                    new PickBranch
                    {
                        Trigger = new Wakeup()
                        {
                            BookmarkName=new InArgument<string>((c)=> BookmarkName.Get(c))
                        },

                        Action = new Assign<bool>()
                        {
                            To= new ArgumentReference<bool> { ArgumentName = "Result" },
                            Value= true,
                        }
                    },
                    new PickBranch
                    {
                        Trigger = new Delay
                        {
                            Duration = new InArgument<TimeSpan>((c)=> GetDuration(AlarmTime.Get(c)))
                        },
                        Action = new Assign<bool>()
                        {
                            To=new ArgumentReference<bool> { ArgumentName = "Result" },
                            Value= false,
                        }
                    }
                    }

            };

    }

    static TimeSpan GetDuration(DateTime alarmTime)
    {
        if (alarmTime < DateTime.Now)
            return TimeSpan.Zero;

        return alarmTime - DateTime.Now;
    }
}

 

Points of Interest

So you have seen examples of crafting activities through deriving Activity, CodeActivity and NativeActivity, as well as dynamically constructing a DynamicActivity instance. You might have noticed the Implementation property. However, the MSDN does not document well about this property in Activity derived classes.

CodeActivity.Implementation says this is not supported. Correct.

NativeActivity.Implementation says this iss the execution logic. However, this is not really supported, as you can try, or review the source code of NativeActivity.

 

While both WaitForSignalOrDelay and WaitForSignalOrAlarm are derived from class Activity for composite activities, MSDN has an example of doing similar things through deriving from NativeActivity. While this MSDN example demonstrates what already supported by DynamicActivity, you seem to be able to craft WaitForSignalOrDelay through deriving from NativeActivity, using similar design of the MSDN example. However, it will looks clumsy to me, because of low level APIs and more complicated algorithm. If you have different ideas, please leave a comment.

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)