SDP工作流循环初探(下)
之前我们分析了为什么一个简单且看似合理的SPD工作流循环为什么有可能在执行过程中跳出循环的原因,我们的目的在于让这个工作流变得更加稳定。
当一个工作流无法正常工作的时候,可能就会需要其他工作流的帮助。一个人干不了的活,大家一起干。我们需要有另外一个工作流实例,来对第一个工作流进行“监督和提醒”。另外一个工作流即可以放在同一个列表上(因为它们是不同的模板,可以同时产生两个不同的实例),或者另一个辅助列表上。放在同一个列表上在列表设计上比较简单,但是因为同时有两个不同的工作流实例在运行,分析和工作流设计要格外小心;放在多个列表上的话,每个列表上只运行一个实例,通过一个字段关联起来,逻辑上更容易分析,但一旦涉及到迁移和部署就会非常麻烦(见老赵的这篇文章)。为了做测试,我先把所有工作都堆在一个列表上。
思路是这样的,工作流两个模板分成工人和监工两个角色,工人负责干活(执行具体的操作),监工负责提醒工人。工人执行完操作之后,告诉监工“我干完了!”,然后监工说“哦?那么你可以先休息一下”并且在到点之后提醒工人说“嘿!该干活了!”。实际上,这是两个工作流实例互相触发的过程,并通过一个标志位决定目前是工作者应该执行操作,还是监视者执行操作。当然,为了避免之前所说的“触发在退出之前发生”的情况出现,两边都要有一个确认时间段,等待另外一方结束后自己才开始运行。
这两个工作流的设计如下:
首先是工人:
然后是监工:
其中的Mark就是标记为,决定哪个工作流应该运行,初始值设置成true。执行过程是这个样子的:一开始两边都触发,但因为Mark为true,所以工人开始,监工退出 –> 工人先休息5分钟(偷懒一下) –> 设置标记,触发监工运行 –> 干活后退出 –> 监工被触发后(以为这个实例的触发是由Mark = false引起的,所以监工必然继续运行)先休息5分钟(等工人把活干完并且工人实例退出) –> 设置标记,触发工人实例 –> 工人被触发(同理,它是被Mark = true引起的,所以必然继续运行) –> 休息5分钟(这5分钟的目的就在这里体现出来了,它是在等待监工实例的退出,否则如果监工因为尚未退出没有被工人的后续操作触发,而且工人自己的实例也出现触发早于退出的情况出现时,那么就没有人提醒工人继续工作了) –> ……于是可怜的工人就在监工的控制之下不断地开始计数了。
我们以工人实例的5分钟休息和监工实例的5分钟休息作为代价,换来了两者互相触发的稳定流程。所以这一方法理论上的计数间隔时间是10分钟,据我测试实际时间平均在15分钟左右。
可以高枕无忧了吧任其运行了吧,还不能!为什么呢?在实际的应用中,除了这两者互相触发之外,还可能由用户去修改这个条目,从而唤醒那个已经停止了的实例。让我们来分析一下可能出现的局面:工人正在睡觉(等待监工退出),此时监工已经做完了本职工作退出 –> 就在工人睁开眼睛,准备提醒监工(设置标志位)之前,某个外来用户做了个更新,当头一棒叫醒了监工(因为工人正在运行,所以没有新的工人实例出现) –> 监工懒洋洋地睁开眼睛,发现现在还不应该工作(标志位尚未被工人更新),于是打算退出 –> 而就在监工从醒来发现没有活干到退出的这一段过程中,工人完成了本职工作(设置标记位、计数) –> 这样一来,由于在工人干活的时候监工还没有退出所以新的监工实例没有出现,如果工人干完活退出前就发生了自我提醒的事情(自我触发发生在退出之前),也不会有新的工人实例出现!于是,两个人因为外来者的干扰同时结束了工作,没有人负责计数了!
好吧……我承认这种情况非常极端,我在测试过程中同时开了个Console每隔1-60秒随机更新一下条目作为外部干扰,在流程跑了一整个晚上之后仍然在正常运行(已经计数了31次),但是从理论上来讲,上面的这种情况是仍有可能出现的(如果实例退出时间 > 工人所有的实际操作时间)。为了避免这种情况的出现,我们需要在两个工作流中再次加入等待流程,等待又用户外部干扰产生的意外唤醒退出后再向下执行。
我没有具体做实验,但是设计了这样一种编排方式:
在工人中,将Mark标记设置放在暂停之前,以便提前触发监工;监工等待1天让工人干完事情,然后设置标记触发工人,等待5分钟后再做一个HeartBeat(随便更新一个字段)。监工的5分钟和工人的2小时都是为了避免另一方因为外部操作而被唤醒。我们可以讲用户外部操作这个动作插入上述流程中的每个操作之间,来分析外部操作究竟是否会影响到整个计数流程的进行。因为情况比较多,我就不在这里一一分析了。需要保证的是,监工的第二次暂停必须远小于工人的暂停(因为触发是不规律的,要留一个比较大的浮动范围),同时工人的暂停也要远小于监工的第一次暂停。这个流程理论上的计数间隔是1天零5分钟。
文章在这里得到了一个相当复杂,但几乎cover了所有情况的一个解决方案(我说几乎是以为我在做设计时脑袋都大了,不知道是不是漏过了什么信息)。如果各位看官有更好的解决方案,也欢迎在后面留言。
ps. 这个玩意儿在我这边的实际项目背景是这样的:有一个列表,每个条目都有一个RAG(Red, Amber & Green)状态字段,根据不同情况的不同Date Due,在Date Due内RAG状态为绿色;再过一个月之内,RAG变成黄色;超过一个月后RAG变成红色。可以说这是比较常见的一个应用,但之所以采用SPD工作流的方式进行循环触发的原因,首先是客户不允许编写后台代码(否则timerjob轻松搞定这个需求);其次,SharePoint的计算字段中不能采用Today公式,换句话说SharePoint的计算字段不是在取值时计算,而是在条目更新时计算的(所以就算像某