通过单元测试学习 Windows Workflow Foundation 4.5:状态机
状态机的工作流状态机。
背景
状态机是计算机科学中重要的数据结构概念之一。我相信所有拥有计算机科学学位或软件工程学位的学生都必须在入门课程中学习过状态机,因为多年前我在大学里就给新生教授过这些数据结构。
然而,在我作为商业世界全栈开发人员的职业生涯中,到目前为止,我还没有做过一个使用状态机的商业项目,部分原因是对于需要管理(有限)状态的常见商业应用程序,有很多替代方案:带有 IF 和 SWITCH 语句的逻辑树、递归以及逻辑表。在一些项目中,我曾想用状态机进行设计,因为它很适合状态机,但是,我不想编写自己的状态机框架,并且也认为使用状态机的代码在没有计算机科学或软件工程学位的开发人员看来可能不太容易维护。
尽管如此,从 Windows Workflow Foundation v3.5 版本开始,状态机就已经内置了,并在 v4.5 版本中变得足够成熟/抽象。我希望在下一个能够充分发挥状态机优势的项目中使用它。在谷歌搜索时,我发现关于使用状态机的例子很少,大多数都是过时的,使用的是 WF 3.5 和 4.0 中的已弃用类。所以我编写了这些示例来探索 WF 4.5 中状态机的便利性。
引言
如果您从未学习过状态机并且对此感兴趣,维基百科条目有限状态机可能是一个不错的起点。WF 4.5 中的状态机已经足够成熟和抽象,可以轻松编写易于维护的状态机代码。我将以自动旋转门为例。
旋转门状态机工作流
在项目“BasicWorkflows.csproj”中,您将找到 TurnstileStateMachine.xaml
如您所见,抽象和实现都是通过一种视觉模型驱动的方法在一个地方完成的。
触发是通过使用 Wakeup 类进行书签调用完成的。
测试
[Fact]
public void TestStateMachineWorkflow()
{
var a = new TurnstileStateMachine();
var app = new WorkflowApplication(a);
app.InstanceStore = WFDefinitionStore.Instance.Store;
app.PersistableIdle = (eventArgs) =>
{
return PersistableIdleAction.None; //Must be None so 1 application instance will do all states
};
var id = app.Id;
app.Run();
Thread.Sleep(200); //Run and ResumeBookmark are all non blocking asynchronous calls, better to wait prior operation to finish.
var br = app.ResumeBookmark("coin", null);
Assert.Equal(BookmarkResumptionResult.Success, br);
Thread.Sleep(200);
br = app.ResumeBookmark("coin", null);
Assert.Equal(BookmarkResumptionResult.Success, br);
Thread.Sleep(200);
br = app.ResumeBookmark("push", null);
Assert.Equal(BookmarkResumptionResult.Success, br);
Thread.Sleep(200);
br = app.ResumeBookmark("push", null);
Assert.Equal(BookmarkResumptionResult.Success, br);
Thread.Sleep(200);
br = app.ResumeBookmark("funky", null);
Assert.Equal(BookmarkResumptionResult.NotFound, br);
Thread.Sleep(200);
br = app.ResumeBookmark("coin", null);
Assert.Equal(BookmarkResumptionResult.Success, br);
Thread.Sleep(200);//ResumeBookmark is asynchrounous in a new thread, so better to wait, otherwise they got killed when app.Cancel is executed.
app.Cancel();
}
状态机活动
除了绘制状态机的工作流之外,您还可以在状态机类的实例化过程中通过代码定义状态机的定义。
示例
[Fact]
public void TestTurnstile()
{
var lockedState = new State() { DisplayName = "Locked" };
var unlockedState = new State() { DisplayName = "Unlocked" };
var insertCoinWhenLocked = new Transition()
{
To = unlockedState,
Trigger = new Wakeup() { BookmarkName = "coin" },
};
var insertCoinWhenUnlocked = new Transition()
{
To = unlockedState,
Trigger = new Wakeup() { BookmarkName = "coin" },
};
var pushWhenLocked = new Transition()
{
To = lockedState,
Trigger = new Wakeup() { BookmarkName = "push" },
};
var pushWhenUnlocked = new Transition()
{
To = lockedState,
Trigger = new Wakeup() { BookmarkName = "push" },
};
lockedState.Transitions.Add(pushWhenLocked);
lockedState.Transitions.Add(insertCoinWhenLocked);
unlockedState.Transitions.Add(pushWhenUnlocked);
unlockedState.Transitions.Add(insertCoinWhenUnlocked);
var a = new StateMachine()
{
InitialState = lockedState,
};
a.States.Add(lockedState);
a.States.Add(unlockedState);
var app = new WorkflowApplication(a);
app.InstanceStore = WFDefinitionStore.Instance.Store;
app.PersistableIdle = (eventArgs) =>
{
return PersistableIdleAction.None; //Must be None so 1 application instance will do all states
};
var id = app.Id;
app.Extensions.Add(new Fonlow.Utilities.TraceWriter());
var stp = new StatusTrackingParticipant();
app.Extensions.Add(stp);
app.Run();
Thread.Sleep(200); //Run and ResumeBookmark are all non blocking asynchronous calls, better to wait prior operation to finish.
var br = app.ResumeBookmark("coin", null);
Assert.Equal(BookmarkResumptionResult.Success, br);
Thread.Sleep(200);
Assert.Equal("Unlocked", stp.StateName);
br = app.ResumeBookmark("coin", null);
Assert.Equal(BookmarkResumptionResult.Success, br);
Thread.Sleep(200);
Assert.Equal("Unlocked", stp.StateName);
br = app.ResumeBookmark("push", null);
Assert.Equal(BookmarkResumptionResult.Success, br);
Thread.Sleep(200);
Assert.Equal("Locked", stp.StateName);
br = app.ResumeBookmark("push", null);
Assert.Equal(BookmarkResumptionResult.Success, br);
Thread.Sleep(200);
Assert.Equal("Locked", stp.StateName);
br = app.ResumeBookmark("funky", null);
Assert.Equal(BookmarkResumptionResult.NotFound, br);
Thread.Sleep(200);
Assert.Equal("Locked", stp.StateName);
br = app.ResumeBookmark("coin", null);
Assert.Equal(BookmarkResumptionResult.Success, br);
Thread.Sleep(200);//ResumeBookmark is asynchrounous in a new thread, so better to wait, otherwise they got killed when app.Cancel is executed.
Assert.Equal("Unlocked", stp.StateName);
app.Cancel();
}
public class StatusTrackingParticipant : System.Activities.Tracking.TrackingParticipant
{
public string StateName { get; private set; }
protected override void Track(TrackingRecord record, TimeSpan timeout)
{
var stateMachineStateRecord = record as System.Activities.Statements.Tracking.StateMachineStateRecord;
if (stateMachineStateRecord == null)
return;
StateName = stateMachineStateRecord.StateName;
Trace.TraceInformation("StateName: " + stateMachineStateRecord.StateName);
}
}
提示
StatusTrackingParticipant 类基于文章:创建自定义跟踪参与者。
备注
在实际项目中,状态跟踪可能不需要,因为每个状态中的操作足以表示状态。但是,在开发或调试过程中,添加一些状态跟踪可以确保更好的质量。
Using the Code
源代码可在https://github.com/zijianhuang/WorkflowDemo找到。
必备组件
- Visual Studio 2015 Update 1 或 Visual Studio 2013 Update 4
- xUnit(包含)
- EssentialDiagnostics(包含)
- FonlowTesting(包含)
- 工作流持久化 SQL 数据库,默认本地数据库为 WF。
本文中的示例来自测试类:StateMachineTests。
关注点
如果您搜索“Workflow Foundation 状态机”等,您可能会找到很多信息,但是大多数都是过时的,甚至已经弃用。
- CodePlex 上的 Windows Workflow Foundation基本上是针对 .NET 4 的。
- WF4 状态机实践实验室及相关资料很全面,但是已经过时了。微软显然没有更新 WF4.5 的资料。
我还发现这些文章很有趣