Framework最迷人的一點就是,設計時即考量到了延展性的議題,所以一個良好的Framework,必然擁有可擴充的設計存在,本文的Flow Engine雖然簡單,但也具備了這個特色。
The Framework Designing (5) – Flow Engine Part 2
文/黃忠成
持續延展
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));
}
雖然可以達到所要的效果,但總覺得有些怪怪的,原因是首次取得TextFile1.txt的動作,此動作理論上也該是流程的一部份,另外AddStep的手法也不漂亮,那有沒有更好的方式可以完成同樣的效果呢?
Loop Flow Steps
設計上,Flow Engine有三個擴充點,一個是Flow Context,二是Flow Service、最後一個就是Flow Step,透過設計新的Flow Step,可以在既存的Flow Engine架構下,提供不同的執行模式,
下面的ForFlowStep就是一例。
public class ForFlowStep : FlowStepBase
{
private string _stepName;
private int _seed, _max;
private bool _breakLoop = false;
private ExecuteState _breakReason = ExecuteState.Idle;
private Action _func;
private AutoResetEvent reset = null;
public override string StepName
{
get
{
return _stepName;
}
}
protected override void DoExecute()
{
reset = new AutoResetEvent(false);
Thread th = new Thread((state) =>
{
if (_func != null)
{
try
{
for (int i = _seed; i < _max; i++)
{
_func(i, this);
reset.WaitOne();
if (_breakLoop)
{
_breakLoop = false;
State = _breakReason;
break;
}
}
Complete();
}
finally
{
if (reset != null)
reset.Dispose();
}
}
});
th.IsBackground = true;
th.Start();
}
public void CompleteLoop()
{
if (reset != null)
reset.Set();
}
public void CancelLoop()
{
_breakReason = ExecuteState.Cancel;
_breakLoop = true;
if (reset != null)
reset.Set();
}
public void FailLoop(Exception ex)
{
_breakReason = ExecuteState.Fail;
_breakLoop = true;
Fail(ex);
if (reset != null)
reset.Set();
}
public ForFlowStep(string stepName, int seed, int max,
Action func)
{
_stepName = stepName;
_seed = seed;
_max = max;
_func = func;
}
}
使用方式如下
static void TestForFlow()
{
StaticFlowService service = new StaticFlowService(
new ForFlowStep("1",1,11,
(i,step)=>
{
int baseValue = step.Context.GetParameter("baseValue");
step.Context.SetParameter("baseValue",baseValue + i);
step.CompleteLoop();
}));
service.Context.SetParameter("baseValue", 0);
service.FlowExecuteComplete += (s, arg) =>
{
Console.WriteLine(service.Context.GetParameter("baseValue"));
};
service.ExecuteAsync();
}
相同的手法,要做出For Each的Step也不難。
public class ForEachFlowStep : FlowStepBase
{
private string _stepName;
private bool _breakLoop = false;
private ExecuteState _breakReason = ExecuteState.Idle;
private AutoResetEvent reset = null;
private Action> _loopFunc;
private Func,IEnumerable> _dataSourceProvider = null;
private IEnumerable _dataSource = null;
public override string StepName
{
get
{
return _stepName;
}
}
protected override void DoExecute()
{
reset = new AutoResetEvent(false);
Thread th = new Thread(() =>
{
if (_dataSourceProvider != null)
_dataSource = _dataSourceProvider(this);
if (_dataSource != null)
{
try
{
foreach (var item in _dataSource)
{
_loopFunc(item, this);
reset.WaitOne();
if (_breakLoop)
{
_breakLoop = false;
State = _breakReason;
break;
}
}
Complete();
}
finally
{
if (reset != null)
reset.Dispose();
}
}
});
th.IsBackground = true;
th.Start();
}
public void CancelLoop()
{
_breakReason = ExecuteState.Cancel;
_breakLoop = true;
if (reset != null)
reset.Set();
}
public void FailLoop(Exception ex)
{
_breakReason = ExecuteState.Fail;
_breakLoop = true;
Fail(ex);
if (reset != null)
reset.Set();
}
public void CompleteLoop()
{
if (reset != null)
reset.Set();
}
public ForEachFlowStep(string stepName, IEnumerable dataSource, Action> func)
{
_stepName = stepName;
_dataSource = dataSource;
_loopFunc = func;
}
public ForEachFlowStep(string stepName, Func, IEnumerable> dataSourceProvider, Action> func)
{
_stepName = stepName;
_dataSourceProvider = dataSourceProvider;
_loopFunc = func;
}
}
/t,> /t,>
原先Silverlight的程式就可以改成如下所示。
private void GetWithForEachFlowEngine()
{
StaticFlowService service = new StaticFlowService(new StaticFlowStep("1", (step) =>
{
WebClient client = new WebClient();
client.DownloadStringCompleted += (s, arg) =>
{
if (arg.Error != null)
{
((IFlowStep)arg.UserState).Fail(arg.Error);
return;
}
List imageList = new List();
using (StringReader sr = new StringReader(arg.Result))
{
while (sr.Peek() != -1)
imageList.Add(sr.ReadLine());
}
((IFlowStep)arg.UserState).Context.SetParameter>("imageList", imageList);
((IFlowStep)arg.UserState).Complete();
};
client.DownloadStringAsync(new Uri("/TextFile1.txt", UriKind.Relative),step);
}),
new ForEachFlowStep("2", (step) =>
{
return step.Context.GetParameter>("imageList").AsEnumerable();
}, (item, step) =>
{
WebClient client2 = new WebClient();
client2.OpenReadCompleted += (s1, arg1) =>
{
if (arg1.Error != null)
{
((ForEachFlowStep)arg1.UserState).FailLoop(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);
((ForEachFlowStep)arg1.UserState).CompleteLoop();
}), step);
};
client2.OpenReadAsync(new Uri("/" + item, UriKind.Relative),step);
}));
service.ExecuteAsync();
}
/string>
/string>
截至目前為止,我們達到的需求如下:
1、Step執行產生例外 -- OK |
2、Step與Step間需交換參數—OK |
3、Flow必須回傳結果值 – OK |
4、Step可能會有跳過下一個Step的需求 – OK |
5、Step本身可能需要執行多個Sub Steps - OK |
6、Steps的執行可能是非同步的—OK |
7、Step可能需要跳到特定的Step執行,期間會跳躍過多個Steps – OK |
8、Steps可能需要動態組成 |
9、Steps必須可重用 |