11.29.2016

NetMQ with Rx

NetMQ + Rx + Actor

今天在找Rx的應用時,發現了這一篇–NetMQ+ RX (Streaming Data Demo App 2 of 2)
NetMQ算是C#版的ZeroMq移植,之前一直沒機會瞭解,正好由這篇文章來學一下。
這一篇文章它還用到了Actor Model機制(話說Akka.Net也還沒看…)以避免同步鎖定問題,這也要花上一段時間去學。

Anyway, 這篇文章提供了一個Sample程式,消化後再接續說明。

Written with StackEdit.

Yahoo movie parse with Rx

用C#抓網頁資料

最近有個小需求,要用C#抓網頁資料,先找了下解析器,原本是想用nsoup,後來經過比較後還是選擇了HtmlAgilityPack。

先建了Model

public class MovieInfo
{
    public Uri ImageUri { get; set; }
    public string ChineseName { get; set; }
    public string EnglishName { get; set; }
    public DateTime ReleaseDateTime { get; set; }
    public string BriefDescription { get; set; }
    public string FullyDescription { get; set; }

    public override string ToString()
    {
        return string.Format("Movie [{0}], [{1}], [{2}], [{3}], [{4}]", 
            ChineseName, 
            EnglishName, 
            ReleaseDateTime,
            BriefDescription, 
            ImageUri);
    }
}

而Parser的部份(原本是要和ScrapySharp一起使用,後來看到範例可以簡單的解析,就沒有另外學了,話說XPath要花點時間瞭解下才對)

先看奇摩電影上的html結構

<div class="item">
    <div class="img">
        <a href="https://tw.rd.yahoo.com/referurl/movie/thisweek/info/*https://tw.movies.yahoo.com/movieinfo_main.html/id=6344">
            <img src="https://s.yimg.com/vu/movies/fp/mpost4/63/44/6344.jpg" title="死亡筆記本:決戰新世界">
        </a>
    </div>
    <div class="text">
        <h4>
            <a href="https://tw.rd.yahoo.com/referurl/movie/thisweek/info/*https://tw.movies.yahoo.com/movieinfo_main.html/id=6344">死亡筆記本:決戰新世界</a>
        </h4>
        <h5>
            <a href="https://tw.rd.yahoo.com/referurl/movie/thisweek/info/*https://tw.movies.yahoo.com/movieinfo_main.html/id=6344">Death
                Note Light up the NEW world</a></h5>
        <span class="date">上映日期:<span>2016-11-25</span></span>
        <p>
            ★ 史上最經典鬥智推理代表作《死亡筆記本》,電影版十年後全新篇章再起! ★ 《寄生獸》東出昌大 X 《紙之月》池松壯亮 X 《暗殺教室》
            <ins>...<a href="movieinfo_main.html/id=6344" hpp="thisweek-guide">詳全文</a></ins>
        </p>
        <div class="clearfix">
            <ul class="links clearfix">
                <li class="intro"><a
                        href="https://tw.rd.yahoo.com/referurl/movie/thisweek/info/*https://tw.movies.yahoo.com/movieinfo_main.html/id=6344">電影介紹</a>
                </li>
                <li class="trailer"><a
                        href="https://tw.rd.yahoo.com/referurl/movie/thisweek/trailer/*https://tw.movies.yahoo.com/video/死亡筆記本-決戰新世界-中文版預告-015209257.html">預告片</a>
                </li>
                <li class="photo"><a
                        href="https://tw.rd.yahoo.com/referurl/movie/thisweek/photo/*https://tw.movies.yahoo.com/movieinfo_photos.html/id=6344">劇照</a>
                </li>
                <li class="time"><a
                        href="https://tw.rd.yahoo.com/referurl/movie/thisweek/time/*https://tw.movies.yahoo.com/movietime_result.html/id=6344">時刻表</a>
                </li>
            </ul>
        </div>
    </div>
</div>

可以發現每部電影都是放在div class=”item”中的,所以解析的程式就從這個節點開始處理,下列程式碼中的nodes就是當頁所有電影的資訊,在Parsing時,順便用看看Parallel.ForEach這個功能,不過目前並沒有實際量測總花費時間。

public static class YahooMovieParser
{
    private static readonly object Sync = new object();
    public static List<MovieInfo> Parse(string webContent)
    {
        var html = new HtmlAgilityPack.HtmlDocument();
        html.LoadHtml(webContent);

        var root = html.DocumentNode;
        var nodes = root.Descendants()
            .Where(n => n.GetAttributeValue("class", "").Equals("item"));

        var movieInfos = new List<MovieInfo>();

        // if not consider the order
        Parallel.ForEach(nodes, node =>
        {
            var mi = new MovieInfo();

            var divImg = node
                .Descendants().Single(n => n.GetAttributeValue("class", "").Equals("img"))
                .Descendants("a").Single()
                .Descendants("img").Single()
                .Attributes[0].Value;

            var divText = node.Descendants()
                .Single(n => n.GetAttributeValue("class", "").Equals("text"));

            var cname = divText
                .Descendants("h4").Single()
                .Descendants("a").Single()
                .InnerText;

            var ename = divText
                .Descendants("h5").Single()
                .Descendants("a").Single()
                .InnerText;

            var rDate = divText
                .Descendants("span").FirstOrDefault()
                .ChildNodes[1].InnerText;

            var briefDescription = divText
                .Descendants("p").Single()
                .FirstChild
                .InnerText;

            mi.ImageUri = new Uri(divImg);
            mi.ChineseName = cname;
            mi.EnglishName = ename;
            mi.ReleaseDateTime = DateTime.ParseExact(rDate, "yyyy-MM-dd", CultureInfo.InvariantCulture);
            mi.BriefDescription = briefDescription;

            lock (Sync)
            {
                movieInfos.Add(mi);
            }
        });
        return movieInfos;
    }
}

不過這些不是重點,重點是試用目前正在學的Rx的方式來完成,如下

private IObservable<List<MovieInfo>> ParseMovie(string url)
{
    var wc = new WebClient() { Encoding = Encoding.UTF8 };

    // when received the download completed event, 
    // parse the data and return to the caller
    IObservable<List<MovieInfo>> observable = Observable
        .FromEventPattern<DownloadStringCompletedEventArgs>(wc, "DownloadStringCompleted")
        .Select(item =>
        {
            var data = item.EventArgs.Result;
            return YahooMovieParser.Parse(data);
        });

    wc.DownloadStringAsync(new Uri(url));

    return observable;
}

話說我之前在知道可以用var後,就儘量使用它,不過後來又看到有不同的意見,想了想後,就決定在很明顯的地方才用var宣告型態,因為像上面的Observable.FromEventPattern的回傳型態,我又用了select,無法光看就知道實際型態(要移動滑鼠讓IDE顯示)。

程式中的observable,取代了我們原本在採用非同步呼叫時所用的callback等方式,指定在完成後取得結果,並執行解析動作,讓後來的訂閱者(如下方程式碼中的iCanBeDisposed)取用.

private void Form1_Load(object sender, EventArgs e)
{
    // parse movie every second, output the result when finished.
    IDisposable iCanBeDisposed = Observable.Interval(TimeSpan.FromSeconds(10))
        .ObserveOn(SynchronizationContext.Current)
        .Subscribe(count =>
        {
            ParseMovie(URL_THIS_WEEK)
                .Subscribe(movieInfos =>
                {
                    listBox1.Items.Clear();
                    listBox1.Items.Add("本週新片");
                    listBox1.Items.Add("Count: " + count);

                    foreach (var item in movieInfos)
                    {
                        listBox1.Items.Add(item.ToString());
                    }
                });

            ParseMovie(URL_IN_THEATERS)
                .Subscribe(movieInfos =>
                {
                    listBox2.Items.Clear();
                    listBox2.Items.Add("上映中");
                    listBox2.Items.Add("Count: " + count);

                    foreach (var item in movieInfos)
                    {
                        listBox2.Items.Add(item.ToString());
                    }
                });
        },
        ex => Trace.WriteLine(ex));
}

由於上映中的電影有分頁,之後再試用非同步方式撈出其它分頁的資料,以及電影本身的詳細資料。

11.24.2016

WinForm表單控制項狀態管理 - MVVM(with ReactiveUI) way

用物件導向工具開發程式1

最早最早,所有code都寫在Form中時,狀態的變更,落在各個被觸發的事件中,假設目前我們有一個TextBox,一個Button,程式需求TextBox中至少要n個字元才可以觸發Button的Click事件,於是我們在TextBox的TextChanged事件中加上判斷,再設定按鈕Enabled,搞定。

但事情發生在多個控制項,多個狀態時,複雜度隨之而來。

開發物件導向程式

於是,換個方向,使用狀態Property,在它的setter中寫判斷邏輯,看起來很好,但還是跟Form綁太緊了。尋找進階版,Mediator浮現,嗯嗯,符合SRP原則,可是好像不夠OCP,沒關係,再抽象一次…
發現有人寫好了(UI State Synchronization of WinForm Controls)

_stateManager
    .AddCommand(CMD_INDEX_CHECKING, UIObject.CreateObject(this.btnRemTarget));
_stateManager
    .AddCommand(CMD_INDEX_CHECKING, UIObject.CreateObject(this.btnSpiralTest));
_stateManager
    .AddCommand(CMD_INDEX_CHECKING, UIObject.CreateObject(this.btnIndexCheck));

_stateManager
    .AddCommand(CMD_SPIRAL_CHECKING, UIObject.CreateObject(this.btnRemTarget));
_stateManager
    .AddCommand(CMD_SPIRAL_CHECKING, UIObject.CreateObject(this.btnSpiralTest));
_stateManager
    .AddCommand(CMD_SPIRAL_CHECKING, UIObject.CreateObject(this.btnIndexCheck));

UI層的狀態維護,交給Mediator負責,而這個可重用的Mediator中透過字典記錄使用者定義的命令及相對應的控制項,並依需要設定控制項的狀態,這種方式下再配合Model-View-Presenter模式,UI和Presenter不再耦合,Presenter中也不用再出現WinForm相關的參考,perfect!

那…為什麼要ReactiveUI?

因為它支援.Net目前大多數的Presentation,in MVVM way.
GitHub測試

View

var context = SynchronizationContext.Current;
VM = new ViewModel2(
    context,
    this.WhenAnyValue(x => x.textBox1.Text),
    this.WhenAnyValue(x => x.textBox2.Text)));

// bind ViewModel's property to control
this.Bind(VM, x => x.UserName, x => x.textBox1.Text);
this.Bind(VM, x => x.Password, x => x.textBox2.Text);

// extra bind, let a property in ViewModel determinate the state of a control
VM.CanUserLogin.BindTo(this, x => x.btnLogin.Visible);

ReactiveUI提供了一個額外的綁定功能,如上將CanUserLogin綁定至控制項的屬性中。而其它的Bind動作,若是在xaml系的表單上,可以直接指定。

ViewModel, 實作商業邏輯

name.ToProperty(this, x => x.UserName, out _userName);
password.ToProperty(this, x => x.Password, out _password);

CanUserLogin = this.WhenAnyValue(
    x => x.UserName, x => x.Password,
    (user, pass) =>
        !string.IsNullOrWhiteSpace(user) &&
        !string.IsNullOrWhiteSpace(pass) &&
        user.Length >= 2 && pass.Length >= 3)
    .DistinctUntilChanged();

Written with StackEdit.

11.17.2016

ReactiveUI-MessageBus

MessageBus

這篇文章介紹了ReactiveUI Message Bus的基本概念,它的最簡用法大致如下程式碼所示─

var cur = MessageBus.Current;
cur.Listen<int>().Subscribe(i=>Console.WriteLine("value is {0}", i));

cur.SendMessage(1);
cur.SendMessage(2);

基本上,這其實類似設計模式中的Mediator模式,或者是所謂的Event-Broker方式,根據之前的經驗,應能有效解耦各類別之間的關系,不過有位仁兄有不同的意見MVVM anti-pattern: Using UI Message Bus communicate between ViewModels & Services,不過我目前的經驗還沒辦法理解他的說明,也許從之後的實作再來看。

而在ReactiveUI-Sample中,提供的測試碼為─

   public class MainViewModel : ReactiveObject
    {
        public MainViewModel()
        {
            Publisher = new PublisherViewModel();
            Subscriber = new SubscriberViewModel();
        }
        public PublisherViewModel Publisher { get; set; }
        public SubscriberViewModel Subscriber { get; set; }

    }

    public class PublisherViewModel : ReactiveObject
    {
        public PublisherViewModel()
        {
            PublishCommand = ReactiveCommand.Create();
            MessageBus.Current.RegisterMessageSource(PublishCommand);
        }

        public IReactiveCommand<object> PublishCommand { get; protected set; }
    }

    public class SubscriberViewModel : ReactiveObject
    {
        public SubscriberViewModel()
        {
            MessageBus.Current.Listen<object>().Subscribe(_ =>
            {
                Value++;
            });
        }


        private int _Value;

        public int Value
        {
            get { return _Value; }
            set { this.RaiseAndSetIfChanged(ref _Value, value); }
        }

    }

主要是這兩個類別
-PublisherViewModel,出版/發行者
-SubscriberViewModel,訂閱者

由出版者註冊命令,訂閱者訂閱當命令觸發時預執行的動作,雙方互相沒有關係,僅透過MessageBus作實際上的溝通。

Written with StackEdit.

11.10.2016

State machine component set

最近將一個Delphi的狀態機元件 - TStateMachine轉成C#版,放在Github
State machine

類別架構圖
enter image description here
所有可視化流程結點都是繼承自TStateControl,TStateControl本身為不可視元件,僅提供共用的基本屬性。

使用時,要先放置一個TStateMachine元件當做其它所有元件的Parent,再依需求放置流程節點,程式開始動作是透過TStateMachine.Execute(),另外加上了Pause()函式,可以看到上方執行後會在’Wait for call’那邊等待。
TStateMachine中使用唯一的一個執行緒來控制流程 - TStateThread,核心動作如下所示:

protected void Execute()
{
    StateMachine.ThreadStart();
    try
    {
        DoStart();
        _state = NewState;
        // Execute state transitions
        while(_state!=null)
        {
            // do
            DoEnterState(_state);
            _pauseEvent.WaitOne(Timeout.Infinite);

            if (_shutdownEvent.WaitOne(0))
                break;                
            Thread.Sleep(100);

            // Get next default state unless state was explicitly changed
            if (NewState == _state)
            {
                NewState = _state.Default();
                OldState = _state;
            }
            _state = null;
            DoExitState(OldState);

            _pauseEvent.WaitOne(Timeout.Infinite);
            _state = NewState;
            if (_shutdownEvent.WaitOne(0))
                break;

            DoTransition();
        }
        DoStop();
    }
    finally
    {
        StateMachine.ThreadStop();
    }
}

之後的目標是試著使用TPL或Reactive Extension來實作目前的架構,以及加上子狀態機?的功能

11.07.2016

ReactiveUI.Fody

在試用ReactiveUI時,對RaiseAndSetIfChanged的方式覺得很麻煩,雖然可以用snippet的方式減少輸入量,但仍沒有{get;set;}那樣簡潔,後來想到Fody有沒有支援,結果還真的有

ReactiveUI.Fody

private string _searchId;

public string SearchId 
{
    get { return _searchId; }
    set { this.RaiseAndSetIfChanged(ref _searchId, value); }
}

to

[Reactive]public string SearchId { get; set; }

神奇的Weave!

11.05.2016

Reactive Extension(Rx)使用

(這不是BCB的RX)

MSDN Reactive Extension 介紹

Reactive Extensions概要

Reactive Extension入門

textBox的文字在TextChanged事件發生時同步

var res = from evt in Observable.FromEventPattern(textBox1, "TextChanged")
          select ((TextBox) evt.Sender).Text;
res.Subscribe(text => textBox2.Text = text);

在Winform表單上繪圖

將滑鼠事件轉成Observable集合後,籍由Subscribe訂閱事件發生時想要的操作,完成一般要分散在三個滑鼠事件中撰寫的工作。

private Point _startPoint;

private void Form1_Load(object sender, EventArgs e)
{
    WireUpEvents();
}

private void WireUpEvents()
{
    var mouseDown = Observable.FromEventPattern<MouseEventArgs>(this, "MouseDown")
        .Select(arg => arg.EventArgs.Location);

    var mouseMove = Observable.FromEventPattern<MouseEventArgs>(this, "MouseMove")
        .Select(arg => arg.EventArgs.Location);

    var mouseUp = Observable.FromEventPattern<MouseEventArgs>(this, "MouseUp")
        .Select(arg => arg.EventArgs.Location);

    var mouseDrag = mouseMove
        .SkipUntil(mouseDown)
        .TakeUntil(mouseUp)
        .Repeat();

    // remember the point when mouse down
    mouseDown.Subscribe(pos => _startPoint = pos);

    // show dragging position
    mouseDrag.Subscribe(pos => label1.Text = pos.ToString());
    mouseDrag.Subscribe(currentPos =>
    {
        using (var g = this.CreateGraphics())
        {
            if (_startPoint == currentPos)
                return;

            g.DrawLine(new Pen(Color.Green), _startPoint, currentPos);
            _startPoint = currentPos;
        }
    });
}

另有UI整合的Framework,也是很有趣的一個應用
ReactiveUI(MVVM with Rx)

11.03.2016

Functional Programming


最近看到這個不錯的函數式編程概念的教學,Functional Programming,我把它作成xmind筆記


作者主要說明了函式本身的

  • Method Signature Honesty
  • Immutability
以及如何處理Primitive Obsession、避免在Mathmatical Function中使用Exception等觀點,另也提供了一個Nuget套件。