The Framework Designing (5) – Flow Engine Part 2

Framework最迷人的一點就是,設計時即考量到了延展性的議題,所以一個良好的Framework,必然擁有可擴充的設計存在,本文的Flow Engine雖然簡單,但也具備了這個特色。

The Framework Designing (5) – Flow Engine Part 2

 

/黃忠成

 

持續延展

 

   Framework最迷人的一點就是,設計時即考量到了延展性的議題,所以一個良好的Framework,必然擁有可擴充的設計存在,本文的Flow Engine雖然簡單,但也具備了這個特色。
   還記得前篇文章如何依序讀取網路圖片並依序顯示出來的嗎?那時我們使用的是以下的程式碼。
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必須可重用