SPD工作流循环初探(上)

作为SharePoint工作流的一个重要编写工具,SPD(SharePoint Designer)以其良好的界面拖拽方式和较为丰富的逻辑、动作越来越多地被用于实际项目的开发应用中。它比SharePoint内置的若干工作流更为强大,功能更完善;而又比Visual Studio的工作流更为简单,几乎不需要了解什么关于WF的知识和概念。

然而,SPD也有很多不完善的地方(这也是VS工作流的生存空间),例如SPD工作流是一个串行流程,换句话说它是一个顺序工作流,而不是一个状态机工作流(虽然它也可以通过某些方式实现状态机工作流的功能,但这不在本文的讨论范围之内);又比如说它不支持循环,即满足某些条件的时候跳回到之前的步骤重新执行——这正是本文要解决的问题。

其实很多文章都提到了关于SPD工作流循环的问题——当然,那些文章的目的是要避免工作流的循环和死锁。先来看一下为什么工作流会产生循环死锁。例如,我们在SPD工作流中设置了当条目变更时自动启动,然后在流程中修改了当前条目的某个字段——于是,这一修改再次自动触发了这一流程,于是工作流开始循环。这是最为简单的一种循环的触发形式,更多的形式比如一个条目的两个工作流互相触发、多个列表之间的工作流互相触发等等。

在那些文章中也都提到了要如何去避免这一循环,其实很简单,加上一个标记位就可以了,用于标记当前这一条目变更是由工作流本身引起的,还是由用户的操作所引起的。在进入工作流第一步,就要先判断这个标记位,如果当前的变更是由工作流引起的,则直接结束工作流,从而避免循环;否则,进行应有的操作,并且在操作后(或前,没有影响),将标记位设置成工作流变更。当然,这一方法需要在用于操作的时候,讲标记位恢复成“用户操作引起工作流”,以便正常触发SPD中的流程。

于是我们不禁想到,这一循环触发的特性是否能够被我们用来进行循环的设计。答案是肯定的,也有若干文章提到了这一点,具体的方法就是在流程中更改当前条目即可。

接下来,我会用一个自动计数的场景来分析一下SPD工作流究竟应该如何进行循环。自动计数的场景非常简单,就是每隔一段时间将条目的某个字段的值自动增量,这用到了SPD中的一个暂停功能、计算功能以及给字段赋值的功能。

我们先来看一下在那些文章中是如何实现这个循环的,SPD工作流的流程如下(当然不要忘了设置成条目变更时自动触发):

image

先来简单的解释一下这一流程,工作流开始的时候暂停5分钟作为计数的间隔,接下来将计数字段进行增量,存储到工作流变量中,然后讲这个变量重新赋值给计数字段。很简单的一个循环,不过先来分析一下其中的一些细节:

(1)关于暂停时间。我们将暂停时间设置为5分钟,但真的就是5分钟就会触发一次么?经实际测试,触发的时间大约在10-20分钟内不等,即使设置成1分钟,也是这个触发几率。一方面,这是由于工作流的默认timer间隔就是5分钟,并不会那么精准的触发;另一方面,这是由.Net 3.0中工作流的一个bug造成的,微软也给出了补丁(见这里),但实际上这个补丁解决的问题是暂停后不再触发的问题。目前我还没有找到能让实际暂停时间缩短到10分钟之内的方法……

(2)也许我们会有一个疑问:为什么先暂停后计数?这和我们常规的思维有些不同,我们通常会觉得应该先计数后暂停,毕竟计数才是这个工作流应该完成的“本分工作”。这个具体的原因我们先放在一边,先看看这个工作流执行的具体状况。

实际测试中,这样一个5分钟间隔的计数器可能跑了大约1-5次之后就停掉了(工作流完成),我做的另一个20分钟间隔的计数器最多的一个条目在运行了15次后也停掉了,于是我做了一个没有间隔的计数器(去掉暂停的步骤)在运行了76次之后也停了……为什么?这个循环中有问题么?

在这里,必须要先明确一些和SharePoint工作流相关重要概念,即工作流模板、工作流关联和工作流实例。所谓的工作流模板就是我们设置的一套规则,工作流如何如何运行,根据什么条件进行分支,进行什么要的操作;工作流关联指的是工作流模板和具体列表、列表条目的绑定关系,即这个工作流在哪个列表上运行;工作流实例是一个工作流模板在一个列表条目上开始运行后产生的实例(用虽然有些偏差但更容易理解的另一个说法,模板相当于类,而实例相当于对象)。SPD设计的工作流实际上是同时完成了模板和关联的操作,开始运行后产生实例。一个非常关键非常重要的地方在于,一个工作流模板关联在一个列表条目上之后,同一时间内只能有一个实例在运行。当已经有实例正在运行的时候,任何操作都不会触发新的实例。

这就解释了我们为什么要把暂停放到最开始的原因。看吧,如果暂停放在最后会怎样:开始计算、复制–>条目变更 –> 试图触发新的工作流实例 –> 引擎发现当前实例还没结束(因为进入了暂停状态,这个实例在睡觉!)所以这一触发被忽略掉 –> 暂停结束,没发生其他的事情 –> 工作流完成。于是这个计数只是在我们修改的时候增量了一次就结束掉了。

但是这依然不能解释为什么暂停放在最前面也会出现工作流停止,退出循环的问题。据我猜测(没有经过证实,但是和Kaneboy讨论后得出的这个结论)是因为工作流的完成需要一定的时间,换句话说,我们应当在头脑中给每个工作流的最后加上一个“退出”的操作,用来结束掉当前的工作流实例。因此,上面那个计数器在运行时就会变成:暂停(当前实例在睡觉) –> 睡醒了,开始干活 –> 计数,条目变更 –> 慢着!事情就在这里出现变化了。这个时候因为条目有了变化引擎想要触发新的实例,但是旧的实例正在退出,究竟是退出快还是触发快?如果退出发生在触发之前,那么触发的时候因为没有实例在运行,新的实例就会顺利产生,进入下一轮计数;如果触发发生在退出之前,那么触发时仍有实例在活着,引擎拒绝生产出新的实例,等到旧实例退出后,工作流就完成了,计数停止。

看!这个简单的计数器并不一定能如我们想象那样的顺利运行。目前我在一台服务器上运行了一个时间间隔为一天的工作流(实际应用中,我们一般也都是以天为单位来工作的),不知道能跑多少天。

文章写到这里并没有结束,因为我们的目的并不是分析它为什么停止,而是要设计出一个不会停止的循环。(就像你对客户说,我知道这个地方为什么出错了!但客户并不会因此就付给你钱,客户 的目的是要你做出一个不会出错的东西)

posted on 2008-11-23 12:02  Erucy  阅读(419)  评论(0编辑  收藏  举报

导航