WF4.0活动模型(1):工作流既活动

如果你已经开发过Windows Workflow Foundation(WF)程序,那么你应该会非常熟悉活动。在WF世界中,程序被定义为一个活动树。有些活动用来控制程序的流,比如Sequence、If和While;有些活动执行特殊的操作,比如Assign、InvokeMethod和WriteLine;还有一些可以和外部系统通讯,比如Send和Receive。

例如下面这个简单的工作流:

image

图1. 一个简单的“扔硬币”工作流

去掉花哨的东西,工作流实际上是一个活动树:

image

图2. 图1中工作流的活动树

活动是WF的基本建模单元,就好比文本编程语言的抽象语法树(Abstract Syntax Tree,AST)。鉴于WF使用了运行时来直接执行活动树,活动也可以视为WF的基本执行单元,好比CLR的IL。

然而活动却还有一些独特的特性,使其要比传统的AST或IL更加强大:

  1. 活动是完全可扩展的:任何人都可以创建一个活动,并定义它的语法和语义。有时这些活动被称为“自定义活动”,这是为了与WF内置的活动相区别。我个人认为这个名字并不好,因为诸如Sequence和If这样的“内置”活动除了是有微软开发并包含在.NET Framework中之外,并没有任何特别之处。WF运行时并不会对内置活动有任何特殊的感知。事实上,任何开发人员都可以使用公开的活动API来编写自己的Sequence和If活动,而且在工作流中使用它们时,它们也会工作的很好。
  2. 活动是自描述的:活动定义了它自己的元数据和执行逻辑。从WF运行时的角度来看,活动只是一块包含了自定义行为的数据。WF程序只是这种数据树的一段描述。所以WF编程是纯声明性并且由数据驱动的。WF还提供了一组丰富的API来检查和创建活动树,类似CLR中的反射和反射发出(Reflection Emit)。这些API极大地提升了开发人员进行WF编程时的体验。一个明显的例子就是可视化设计器。WF团队提供了一组设计器,它们可以内置于Visual Studio,也可以脱离Visual Studio。任何人都可以不费力地创建新的可视化设计器(甚至新的文本语言)来构建活动树。

截至今日,.NET Framework中提供的许多“内置”活动是很轻量的。想要创建真实世界应用程序,开发人员就需要编写“自定义”活动来执行他们的任务。这些任务可能包括:

  • 模拟业务逻辑的流控制,例如小组审查,当请求必须被多人审查并同意之后才能审批通过。
  • 自动执行人类任务的活动,比如发送Email或运行一段管理员脚本。
  • 连接其他系统的桥梁,比如使用特殊通讯协议来与Payroll系统通讯的活动。

现在我们知道活动是工作流编程体验的核心。WF开发人员必须真正理解活动。那么就让我们把活动推向舞台中央,来畅谈WF的活动模型吧。

分解活动

WF为开发人员提供了许多类来实现不同需求的活动。

image

图3. 活动模型类的层级

层级的顶端是Activity,它是WF中所有活动的基类。想要定义一个新的呃活动,开发人员需要实现Activity或者它的一个预定义子类的一个具体类,这些父类提供了不同的创作风格:

Activity

除了作为所有活动的基类并提供所有基本属性和方法之外,Activity类还可以用来直接以组合的方式定义活动。当子类直接继承自Activity时,开发人员可以通过创建已有活动的活动树来声明性地描述活动的实现。例如:

class Greet : Activity
{
public Greet()
{
this.Implementation = () => new Sequence
{
Activities =
{
new WriteLine
{
</span>Text = "Hello"
},
new WriteLine
{
Text = "World"
},
}
};
}
}

代码1. Activity类的使用示例

这段代码告诉WF运行时,当Greet运行时,它需要创建一个包含两个WriteLine活动的Sequence活动,并且将Sequence活动作为Greet的内部实现来执行。

当使用这个类来编程时,开发人员不能访问WF运行时的功能,比如书签。但它很简单并且完全是声明性的(译者注:如果不是以编程方式定义内部实现的话)。一旦社区里出现了丰富的活动库时,我们就可以将这种方式作为创建新组合活动的主要方式来使用。

CodeActivity

CodeActivity可以用来创作那些逻辑不存在于现有活动中的活动,它简单到只需要用代码实现一个Execute方法即可。

下面是一个示例:

class PrintFile : CodeActivity
{
protected override void Execute(CodeActivityContext context)
{
using (StreamReader file = new StreamReader("foo.txt"))
{
string line;
while ((line = file.ReadLine()) != null)
{
</span>Console.WriteLine(line);
}
}
}
}

代码2. CodeActivity类的使用示例

使用CodeActivity非常简单,只需要实现它的Execute方法,此方法会在执行活动时被调用。CodeActivity非常适用于创建简单的叶活动。

AsyncCodeActivity

WF的一个主要价值就在于它支持长时间运行和异步服务。对于活动来说,需要执行长时间操作或者等待其他系统输入的活动是很常见的需求。我们不应该在WF的Execute方法里编写阻塞代码,这是因为它会阻止运行时去执行其他工作,并且服务可能会出现伸缩性和可用性问题。正如我门在《A developer’s view of Workflow》一文中的讨论,WF的“延续”(Continuation)就旨在解决这样的问题。一般来说,活动需要使用一组书签API来享受WF 延续的好处。为了节省开发人员的经历,AsyncCodeActivity提供了常见延续模式的高级封装:执行逻辑以代码方式实现,并且只有一个延续点。这种封装遵循了.NET的BeginInvoke模式并且隐藏了书签,以便于开发人员可以轻松地在WF中进行异步编程而不需要学习新的概念。这对于在WF中实现简单的异步IO来说非常完美。我们会在后续的文章中给出示例。

NativeActivity

如果活动的逻辑不能用现有活动的组成来表示,也不能由一个方法(或异步方法)来实现,就需要使用NativeActivity。例如,一些复杂的流控制模型,比如状态机。在开发NativeActivity时,开发人员需要站在WF运行时的高度来思考:他们需要将活动的执行过程视为队列中的计划工作项;他们能够直接访问绝大部分运行时计划功能,比如书签、取消、错误处理等等。使用NativeActivity的学习曲线是陡峭的,开发过程是复杂的。但是它同时又是最强大的活动创作方式,以及观察WF运行时内部的最佳窗口。在后续的日志中,我们将会尝试去创建一个NativeActivity,并深入观察WF运行时。

Activity<T>

有时候,你会希望活动能够“返回”一些值。例如,活动从文件或数据库获取了一些数据,并且需要将这些数据传递给工作流的剩余部分。WF定义了一系列泛型类来达成此目的。你会发现我们上面提到的所有活动类都有一个对应的泛型版本。例如,Activity<T>和Activity非常类似,它也支持组合风格的活动创作模式。但它还有一个名为Result并且类型为T的输出参数(我们需要单独写一篇日志来分析WF的参数概念)。Activity<T>的实现能够在执行期间为这个参数赋值,并且同一个工作流中的其他活动能够使用到这个值。

至于CodeActivity<T>、AsnycCodeActivity<T>和NativeActivity<T>全都拥有类似的情节所以就不再逐个介绍了。

未完待续……

本文翻译自:http://blogs.msdn.com/flow/archive/2010/01/24/lights-camera-activities-windows-workflow-foundation-s-activity-model.aspx

发表评论

电子邮件地址不会被公开。 必填项已用*标注