Flow Engine,也就是我们常听到的【流程引擎】,主要用途用于以特定顺序执行一系列的工作,就概念上而言,Flow Engine非常简单,只是依据特定顺序来执行一系列工作而已
The Framework Designing (5) – Flow Engine Part 1
文/黄忠成
缘起
算算自小公主出生以来,我已有一段蛮长时间未写任何文章了,多数时间都耗在哄小公主睡觉、上课、顾问工作上,实在也没有太多时间可以写文章。随着2011年的结束,一整年度的工作也告一段落,
小公主随着年纪增长,也比较好带了些,我终于有点自己的时间来将过去一年所研究的技术与实践品与各位读者分享。
Flow Engine
Flow Engine,也就是我们常听到的【流程引擎】,主要用途用于以特定顺序执行一系列的工作,就概念上而言,Flow Engine非常简单,只是依据特定顺序来执行一系列工作而已,如图1:
图1
说实话,这跟一般循环所做的事差不多,都是依据特定顺序来执行一系列工作,我们可以用C#轻易地来实现这个概念。
程序1
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication11
{
class Program
{
static void ExecuteFlow(paramsAction[] steps)
{
foreach(varstep insteps)
step();
}
static void Main(string[] args)
{
ExecuteFlow(
() =>
{
Console.WriteLine("1");
},
() =>
{
Console.WriteLine("2");
},
() =>
{
Console.WriteLine("3");
});
Console.ReadLine();
}
}
}
那如果这么简单,为何还要特地写文章来介绍呢?是的,Flow Engine的概念简单的很,但要完善的实现这个概念却不容易,因为在整个Flow的执行过程中可能会发生以下几种情况。
1、Step执行产生例外 |
2、Step与Step间需交换参数 |
3、Flow必须回传结果值 |
4、Step可能会有跳过下一个Step的需求 |
5、Step本身可能需要执行多个Sub Steps |
6、Steps的执行可能是异步的 |
7、Step可能需要跳到特定的Step执行,期间会跳跃过多个Steps |
8、Steps可能需要动态组成 |
9、Steps必须可重用 |
将这些可能发生的情况包含进来的话,程序1绝不会是现在这么简洁,Flow Engine的概念很直觉,但细节却很繁复。
开始– Executing State
就Flow Engine的概念中,至少拥有两个角色,一是Flow,二是Flow Step,每个Flow代表一个流程,而流程中的每个步骤则以Step方式呈现,根据上节列出的可能发生情况中的
第1、4、6点可以估算出Flow中的每个Step必须拥有以下状态,分别代表执行成功、执行中、失败等状态资讯。
//Flow,Flow Step执行状态
public enum ExecuteState
{
Idle, //停滞
Running, //执行中
Complete, //执行完成
Fail, //错误
Cancel, //取消
Jump //跳过此Step
}
整个架构图将会变成如图2
图2
每个Step都拥有一个执行状态,Flow本身也拥有一个计算型的执行状态,其值是统计所有Step执行状态而来。
依据第2条可能发生的情况,Flow Engine需要有一个通道,让各个Step间交换参数用,这设计起来很简单,就是一个Context概念,当Flow执行起来时,会进入一个Context,
而Context负责提供一个容器供Flow及各个Step间交换数据。
public interface IFlowContext
{
Dictionary Parameters { get; }
T GetParameter(stringkey);
voidSetParameter(stringkey, T value);
}
,>
整个架构会变成图3
图3
有了大概的样子后,我们可以开始定义Flow Engine中的Step Interface。
//Flow Step基本界面
public interface IFlowStep
{
//当Step执行完毕后触发
event EventHandler ExecuteComplete;
//当Step执行期间产生未补捉例外时,放置于此处
Exception FailException { get; }
//所有Flow Step共用的Context
IFlowContext Context { get; set; }
//Step Name, 必要字段
string StepName { get; }
//Step执行状态
ExecuteState State { get; }
//下一个要执行的Step Name,如未指定则依序执行
string NextFlow { get; set; }
//执行
void Execute();
//确认完成
void Complete();
//要求取消
void Cancel();
//跳过此Step
void Jump();
//Step执行失败
void Fail(Exception ex);
}
每个Step都拥有一个Context属性,其值是由Flow所指定,当Flow开始执行时,会准备一个Context对象,并于执行Step前指定至Step的Context属性,这样Step于执行期间就有一个容器
可互相交换数据。
依据第6条,Step的执行可能是异步的,所以每个Flow Step除了拥有执行Step用的Execute函数外(Flow会依序调用Step的Execute来执行Step),还必须拥有Complete、Cancel、Fail等函数,
当Step执行成功时,必须负责调用Complete函数,此时该Step状态会变成Complete,Flow以此来判断某个Step是否正确执行成功,再决定是否执行下一个Step。
依据第7条,Step必须拥有跳到特定Step的能力,也就是说当某种条件成立时,要求Flow直接跳到特定的Step执行,此功能透过NextFlow完成,当Step需要跳到某个特定Step时,
需指定NextFlow属性,该值为指定跳跃的StepName,然后调用Jump,此时执行状态会是Jump,当Flow看到此状态时,会依据NextFlow值来跳跃至特定的Step执行。
当Step执行失败时,必须负责调用Fail,此函数应该把执行状态改为Fail,告知Flow此Step执行失败,由Flow决定是要终止所有Flow或是忽略。
当Step执行成功或是失败时,都会触发ExecuteComplete事件,此事件可供使用者或是Flow对象挂载,当执行期间因产生Exception而导致失败时,Exception都会放置于FailException内,
由Flow统一回报。
下列是FlowContext的实践,并没有很复杂。
public class FlowContext : IFlowContext
{
private object _lock = new object();
private Dictionary _parameters = new Dictionary();
public Dictionary Parameters
{
get
{
return _parameters;
}
}
public T GetParameter(string key)
{
lock (_lock)
{
if (!Parameters.ContainsKey(key))
return default(T);
return (T)Parameters[key];
}
}
public void SetParameter(string key, T value)
{
lock (_lock)
{
if (!Parameters.ContainsKey(key))
Parameters.Add(key, value);
else
Parameters[key] = value;
}
}
},>,>,>
接着定义Flow的界面。
//Flow 基础界面
public interface IFlowService
{
//when step is complete or cancel, the event will be fire.
event EventHandler FlowExecuteComplete;
//所有Flow Step共用的Context
IFlowContext Context { get; }
//要执行的Steps
IFlowStep[] Steps { get; }
//执行状态
ExecuteState State { get; }
//执行所有Steps
void Execute();
//执行所有Steps - 异步
void ExecuteAsync();
//等待完成
void WaitingForComplete();
//取消执行
void Cancel();
}
当所有Step执行或因失败而中止,FlowExecuteComplete都会被调用,此时可透过其参数来决定Flow是否正常执行还是异常中止。
public class FlowExecuteCompleteArgs : EventArgs
{
private ExecuteState _state = ExecuteState.Idle;
private Exception[] _failExceptions = null;
public ExecuteState State
{
get
{
return _state;
}
}
public Exception[] Exceptions
{
get
{
return _failExceptions;
}
}
public FlowExecuteCompleteArgs(ExecuteState state)
{
_state = state;
}
public FlowExecuteCompleteArgs(ExecuteState state,Exception[] exceptions)
{
_state = state;
_failExceptions = exceptions;
}
}
当Flow因异常而中止时,此参数的Exceptions将包含所有产生的Exception,那他为何是个数组呢?这是因为第5条需求,Step本身可能包含多个Sub Step,且其可能是以异步执行,
同时执行多个Sub Step,也就是说这种Step执行期间,可能会产生出多个Exception。
Implement FlowStepBase
定义完界面后便可开始实践,界面所显露的只是一个架构的规格,我们虽然可以从界面本身窥探出架构,但通常只是一个概观,所以多数的Framework除提供界面之外,
还会实践一个参考的实践体,这个实践体通常以抽象类方式存在,也就是说Framework以抽象类告知开发者,这些界面黏合的细节,也以这些抽象类简化开发者延伸的难度。
public abstract class FlowStepBase : IFlowStep
{
private IFlowContext _context = null;
private ExecuteState _state = ExecuteState.Idle;
private string _nextFlow = null;
private EventHandler _executeCompleteHandler;
private Exception _failException = null;
public event EventHandler ExecuteComplete
{
add
{
_executeCompleteHandler = (EventHandler)Delegate.Combine(_executeCompleteHandler, value);
}
remove
{
_executeCompleteHandler = (EventHandler)Delegate.Remove(_executeCompleteHandler, value);
}
}
public Exception FailException
{
get
{
return _failException;
}
}
public IFlowContext Context
{
get
{
return _context;
}
set
{
_context = value;
}
}
public string NextFlow
{
get
{
return _nextFlow;
}
set
{
_nextFlow = value;
}
}
public abstract string StepName { get; }
public ExecuteState State
{
get
{
return _state;
}
set
{
_state = value;
}
}
protected abstract void DoExecute();
public void Execute()
{
_state = ExecuteState.Running;
try
{
DoExecute();
}
catch (Exception ex)
{
Fail(ex);
}
}
protected virtual void DoComplete()
{
if (_executeCompleteHandler != null)
_executeCompleteHandler(this, EventArgs.Empty);
}
public void Cancel()
{
_state = ExecuteState.Cancel;
DoComplete();
}
public void Complete()
{
if(_state == ExecuteState.Running)
_state = ExecuteState.Complete;
DoComplete();
}
public void Jump()
{
_state = ExecuteState.Jump;
DoComplete();
}
public void Fail(Exception ex)
{
_failException = ex;
_state = ExecuteState.Fail;
DoComplete();
}
}
实践本身并没有太复杂。
Implement Flow Service
接着实践Flow本身,也就是IFlowService。
public abstract class FlowService : IFlowService
{
private FlowContext _context = new FlowContext();
private IFlowStep[] _steps;
private EventHandler _flowExecuteCompleteHandler;
public event EventHandler FlowExecuteComplete
{
add
{
_flowExecuteCompleteHandler = (EventHandler)Delegate.Combine(_flowExecuteCompleteHandler, value);
}
remove
{
_flowExecuteCompleteHandler = (EventHandler)Delegate.Remove(_flowExecuteCompleteHandler, value);
}
}
public IFlowStep[] Steps
{
get
{
if (_steps == null)
_steps = CreateSteps();
return _steps;
}
}
public IFlowContext Context
{
get
{
return _context;
}
}
public ExecuteState State
{
get
{
foreach (var step in Steps)
{
if (step.State == ExecuteState.Cancel)
return ExecuteState.Cancel;
else if (step.State == ExecuteState.Fail)
return ExecuteState.Fail;
else if (step.State == ExecuteState.Running)
return ExecuteState.Running;
else if (step.State == ExecuteState.Idle)
return ExecuteState.Idle;
}
return ExecuteState.Complete;
}
}
protected abstract IFlowStep[] CreateSteps();
protected virtual void ExecuteComplate()
{
}
public void ExecuteAsync()
{
Thread th = new Thread((state) =>
{
AutoResetEvent resetEvent = new AutoResetEvent(false);
try
{
#if SILVERLIGHT
System.Windows.Threading.DispatcherSynchronizationContext sc =
(System.Windows.Threading.DispatcherSynchronizationContext)state;
#endif
string _jumpStep = null;
for (int i = 0; i < Steps.Length; i++)
{
Steps[i].Context = _context;
Steps[i].ExecuteComplete += (s1, arg1) =>
{
resetEvent.Set();
};
if (_jumpStep != null && _jumpStep != Steps[i].StepName)
{
Steps[i].Jump();
continue;
}
#if SILVERLIGHT
sc.Post((arg1) => {
Steps[i].Execute();
},null);
#else
Steps[i].Execute();
#endif
resetEvent.WaitOne();
if (Steps[i].State == ExecuteState.Fail || Steps[i].State == ExecuteState.Cancel)
break;
_jumpStep = Steps[i].NextFlow;
}
if (_flowExecuteCompleteHandler != null)
{
if (State == ExecuteState.Fail)
_flowExecuteCompleteHandler(this, new FlowExecuteCompleteArgs(State, _steps.Where(a => a.FailException != null).Select(a => a.FailException).ToArray()));
else
_flowExecuteCompleteHandler(this, new FlowExecuteCompleteArgs(State));
}
ExecuteComplate();
}
finally
{
resetEvent.Dispose();
}
});
th.IsBackground = true;
#if SILVERLIGHT
th.Start(System.Threading.SynchronizationContext.Current);
#else
th.Start(null);
#endif
}
public void Execute()
{
string _jumpStep = null;
for (int i = 0; i < Steps.Length; i++)
{
Steps[i].Context = _context;
if (_jumpStep != null && _jumpStep != Steps[i].StepName)
{
Steps[i].Jump();
continue;
}
Steps[i].Execute();
if (Steps[i].State == ExecuteState.Fail || Steps[i].State == ExecuteState.Cancel)
break;
_jumpStep = Steps[i].NextFlow;
}
if (_flowExecuteCompleteHandler != null)
{
if (State == ExecuteState.Fail)
_flowExecuteCompleteHandler(this, new FlowExecuteCompleteArgs(State, _steps.Where(a => a.FailException != null).Select(a => a.FailException).ToArray()));
else
_flowExecuteCompleteHandler(this, new FlowExecuteCompleteArgs(State));
}
ExecuteComplate();
}
public void WaitingForComplete()
{
while (true)
{
if (State == ExecuteState.Complete ||
State == ExecuteState.Fail ||
State == ExecuteState.Cancel)
break;
System.Threading.Thread.Sleep(10);
}
}
public void Cancel()
{
foreach (var step in Steps)
{
try
{
step.Cancel();
}
catch (Exception)
{
}
}
}
}
这里就复杂多了,先看ExecuteState部分,其值是统计所有Step执行状态而来。
public ExecuteState State
{
get
{
foreach(varstep inSteps)
{
if(step.State == ExecuteState.Cancel)
returnExecuteState.Cancel;
elseif(step.State == ExecuteState.Fail)
returnExecuteState.Fail;
elseif(step.State == ExecuteState.Running)
returnExecuteState.Running;
elseif(step.State == ExecuteState.Idle)
returnExecuteState.Idle;
}
returnExecuteState.Complete;
}
}
这里应该不难懂。
FlowService较难的部分在于ExecuteAsync函数,其将以异步方式执行所有Steps,这里使用了AutoResetEvent来维持Step的顺序性。
另外,FlowService设计时也考虑到了Silverlight的情况,Flow Engine在Silverlight/Windows Phone 7下有一个特别的应用,后面我们再讨论这块。
Implement Static Flow
FlowService与FlowStepBase都只是抽象类,离真正可用的类还有一小段路,设计Framework时,应该提供一个简易的实践给开发者,除了让开发者马上可用外,还可协助开发者更了解Framework本体。
using System;
using System.Net;
using System.Collections.Generic;
using System.Linq;
namespace Artemis.FlowEngine
{
///
/// dynamic flow step is a easy-use flow engine facade,
/// allow developer use actions to execute flow.
///
public class StaticFlowStep : FlowStepBase
{
private string _stepName;
private Action _executeAction;
public override string StepName
{
get
{
return _stepName;
}
}
protected override void DoExecute()
{
_executeAction(this);
}
public StaticFlowStep(string stepName, Action executeAction)
{
_stepName = stepName;
_executeAction = executeAction;
}
}
public class StaticFlowService : FlowService
{
private List _steps = new List();
protected override IFlowStep[] CreateSteps()
{
return _steps.ToArray();
}
public void AddStep(IFlowStep step)
{
_steps.Add(step);
}
public StaticFlowService(params IFlowStep[] steps)
{
_steps = steps.ToList();
}
}
}
Static Flow是一组简单的Flow Engine实践,其用法如下所示。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Artemis.FlowEngine;
namespace TestApp
{
class Program
{
static void TestStaticFlow()
{
StaticFlowService service = new StaticFlowService(
new StaticFlowStep("1",(sender)=>
{
int baseValue = sender.Context.GetParameter("baseValue");
sender.Context.SetParameter("baseValue",baseValue + 10);
sender.Complete();
}),
new StaticFlowStep("2", (sender) =>
{
int baseValue = sender.Context.GetParameter("baseValue");
sender.Context.SetParameter("baseValue", baseValue + 10);
sender.Complete();
}));
service.Context.SetParameter("baseValue", 10);
service.Execute();
Console.WriteLine(service.Context.GetParameter("baseValue"));
}
static void TestStaticFlowAsAsync()
{
StaticFlowService service = new StaticFlowService(
new StaticFlowStep("1", (sender) =>
{
int baseValue = sender.Context.GetParameter("baseValue");
sender.Context.SetParameter("baseValue", baseValue + 10);
sender.Complete();
}),
new StaticFlowStep("2", (sender) =>
{
int baseValue = sender.Context.GetParameter("baseValue");
sender.Context.SetParameter("baseValue", baseValue + 10);
sender.Complete();
}));
service.Context.SetParameter("baseValue", 10);
service.ExecuteAsync();
service.WaitingForComplete();
Console.WriteLine(service.Context.GetParameter("baseValue"));
}
static void Main(string[] args)
{
TestStaticFlow();
TestStaticFlowAsAsync();
Console.ReadLine();
}
}
}
Silverlight and Windows Phone 7
Flow Engine在Silverlight与Windows Phone 7这种以异步处理的架构中有特别的用法,默认情况下,当使用WebClient或是调用WCF Service时,设计者通常因为异步的关系,
必须以事件挂载的方式来处理调用后的结果,这在单一调用时不会造成困扰,但如果是循序型调用时,就必须面对事件回调后的复杂性,详情可参考我的另一篇关于Async CTP的文章
http://www.dotblogs.com.tw/code6421/archive/2010/10/31/18696.aspx
但在Flow Engine的协助下,我们可以在没有Async CTP的情况下简化这种循序型调用。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
using System.IO;
using Artemis.FlowEngine;
namespace SilverlightApplication1
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void LoadPic(IFlowStep step)
{
WebClient client2 = new WebClient();
client2.OpenReadCompleted += (s1, arg1) =>
{
if (arg1.Error != null)
{
step.Fail(arg1.Error);
return;
}
Dispatcher.BeginInvoke(new Action((sender) =>
{
BitmapImage bmp = new BitmapImage();
bmp.SetSource(arg1.Result);
Image img = new Image();
img.Source = bmp;
stackPanel1.Children.Add(img);
((IFlowStep)sender).Complete();
}), step);
};
client2.OpenReadAsync(new Uri("/" + step.StepName, UriKind.Relative));
}
private void GetWithFlowEngine()
{
WebClient client = new WebClient();
client.DownloadStringCompleted += (s, arg) =>
{
if (arg.Error != null)
{
((IFlowStep)s).Fail(arg.Error);
return;
}
List imageList = new List();
using (StringReader sr = new StringReader(arg.Result))
{
while(sr.Peek() != -1)
imageList.Add(sr.ReadLine());
}
StaticFlowService service = new StaticFlowService();
foreach (var item in imageList)
service.AddStep(new StaticFlowStep(item,new Action(LoadPic)));
service.ExecuteAsync();
};
client.DownloadStringAsync(new Uri("/TextFile1.txt", UriKind.Relative));
}
private void button1_Click(object sender, RoutedEventArgs e)
{
GetWithFlowEngine();
}
}
}
截至目前为止,我们达到的需求如下:
1、Step执行产生例外 -- OK |
2、Step与Step间需交换参数—OK |
3、Flow必须回传结果值 – OK |
4、Step可能会有跳过下一个Step的需求 – OK |
5、Step本身可能需要执行多个Sub Steps |
6、Steps的执行可能是异步的—OK |
7、Step可能需要跳到特定的Step执行,期间会跳跃过多个Steps – OK |
8、Steps可能需要动态组成 |
9、Steps必须可重用 |
之后的文章会一一实现这张表中的需求,而且,是在不改变所有界面定义的情况下延展,下次再见了。
范例下载
http://www.code6421.com/BlogPics/Artemis_FlowEngine.zip
原文:大专栏 The Framework Designing (5) – Flow Engine Part 1