65.9K
CodeProject 正在变化。 阅读更多。
Home

通过工作流构建 Activity WaitForSignalOrDelay

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (4投票s)

2016年4月8日

CPOL

2分钟阅读

viewsIcon

11312

等待信号或延迟

引言

假设你已经阅读过这些文章或类似的教程

除了编写代码,你还可以通过 Workflow/XAML 进行声明式编程来开发新的活动。

Using the Code

源代码可在https://github.com/zijianhuang/WorkflowDemo找到。

必备组件

  1. Visual Studio 2015 Update 1 或 Visual Studio 2013 Update 3
  2. xUnit(包含)
  3. EssentialDiagnostics(包含)
  4. 工作流持久化 SQL 数据库,默认本地数据库为 WF。

本文中的示例来自一个测试类:WorkflowApplicationTests。

WaitForSignalOrDelay

WaitForSignalOrDelay 活动可以等待传入的书签调用或延迟一段时间。

唤醒 (Wakeup)

    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);
        }

    }

这是一个最简单的书签活动,不需要书签值和返回值。

通过 XAML 组合

因此你只需要 2 个阻塞活动:Wakeup 和 Delay 通过 Pick 并行运行。 任何一个活动被唤醒,Sequence 就会继续执行。

为了使工作流可用作组件,定义了 2 个 InArguments。 唤醒调用返回 true,持续时间到期返回 false。

测试

        [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"]);
        }

在另一个工作流中使用 WaitForSignalOrDelay 活动/工作流

WaitForSignalOrAlarm

这个类与 WaitForSignalOrDelay 非常相似,并且通过 C# 编码构建。 该活动将在特定的 DateTime 上继续执行,而不是在延迟一段时间后。

    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;
        }
    }

 

关注点

所以你已经看到了通过继承 Activity、CodeActivity 和 NativeActivity 构建活动示例,以及动态构造 DynamicActivity 实例。 你可能注意到 Implementation 属性。 但是,MSDN 对 Activity 派生类中的此属性的文档不够完善。

CodeActivity.Implementation 说明这不受支持。 正确。

NativeActivity.Implementation 说明这是执行逻辑。 但是,这实际上不受支持,你可以尝试一下,或者查看 NativeActivity 的源代码

 

虽然 WaitForSignalOrDelay 和 WaitForSignalOrAlarm 都从 Activity 类派生用于复合活动,但 MSDN 有 一个通过继承 NativeActivity 实现类似功能的示例。 虽然此 MSDN 示例演示了 DynamicActivity 已经支持的内容,但你似乎可以使用与 MSDN 示例类似的设计,通过继承 NativeActivity 构建 WaitForSignalOrDelay。 但是,我认为它看起来会很笨拙,因为使用了低级 API 和更复杂的算法。 如果你有不同的想法,请留下评论。

 

© . All rights reserved.