通过工作流构建 Activity WaitForSignalOrDelay
等待信号或延迟
引言
假设你已经阅读过这些文章或类似的教程
- 通过单元测试学习 Windows Workflow Foundation 4.5:CodeActivity
- 通过单元测试学习 Windows Workflow Foundation 4.5:InvokeMethod 和 DynamicActivity
- 通过单元测试学习 Windows Workflow Foundation 4.5:WorkflowApplication 持久化
除了编写代码,你还可以通过 Workflow/XAML 进行声明式编程来开发新的活动。
Using the Code
源代码可在https://github.com/zijianhuang/WorkflowDemo找到。
必备组件
- Visual Studio 2015 Update 1 或 Visual Studio 2013 Update 3
- xUnit(包含)
- EssentialDiagnostics(包含)
- 工作流持久化 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 和更复杂的算法。 如果你有不同的想法,请留下评论。