3/11/2010 3:00:58 AM

[Tips] 用 “磁碟鏡像" 更換硬碟 #2, Windows 2003 的跨距磁區

543 | TROUBLE SHOOTING | 小技巧 | 技術隨筆 Facebook Share

上一篇,介紹了如何用windows server内建的磁碟鏡像 (Mirror) 更換硬碟後,這次剛好有windows 2003,就拿來試了一下...

廢話就不多說了,先來看一下 2003 的步驟,再來看看跟 2008 / 2008R2 差在那裡:

  1. 原本的樣子,磁碟1 (8.00GB) 是舊的硬碟,磁碟2 (16.00GB) 是要換上來的新硬碟:
    image001

  2. 磁碟1 + 磁碟2 做成鏡像 (MIRROR):
    image002


    等待重新同步化 (Resync) 完成:
    image003


  3. Resync 完成後,移除鏡像:
    image004


    移除鏡像完成後的狀態:
    image005 

  4. 用 2003 的延伸磁區,把磁碟2後面沒用到的空間也併進 D: 來
    image006


    合併之後的狀態:
    image007 



整個步驟跟前一篇都差不多,主要都是靠鏡像(MIRROR)搬完資料後,再把新硬碟多的可用空間併進來。唯一的差別就在這裡: 2003 的 "延伸磁區" (英文: extend volume) 是可以把兩個硬碟,或是兩個分割區併在一起使用。在磁碟管理員還是看的到這些分割區的存在。這就有點像 JBOD (Just a Bunch Of Disks) 的模式。

不過在 2008 之後 (其實 vista 也是),這個功能就改了。原本的名字 extend volumn 現在改成直接擴大原分割區,把原分割區後面可用的空間都納進來,就像你砍掉再重建一個大的分割區一樣,只是資料會留著不會掉。當然有 extend 也有相對的 shrink volume, 這功能會把分割區縮小,騰出空間來讓你多切一個分割區...。而原本 JBOD 模式的功能,則改為 span disk, 用起來效果就如上圖一樣,當然你願意的話,也可以把多顆硬碟 (可以不同容量) 通通併成一個來使用,最多可以併到 32 顆硬碟...。

沒經過這次更換硬碟,還真沒發現 2008 / vista 總算內建這組 extend / shrink volume 的功能進來。雖然很陽春,不過已經很實用了,在過去這種動作是得搬出像 partition magic 這類軟體才能做的到,而這種東西每次用起來心裡都會毛毛的,深怕一不小心就把資料都給毀了...。

這篇小品文章就記到這裡,希望有幫到需要的人 :D



3/6/2010 2:54:22 AM

[Tips] 用 “磁碟鏡像" 無痛更換硬碟

543 | TROUBLE SHOOTING | 小技巧 | 技術隨筆 | 敗家 Facebook Share

老是寫一堆像外星文,沒人看的懂的 multi-threading 文章,偶爾也來換換口味吧。前陣子把 SERVER 的兩顆 750GB HDD (RAID1) 升級了一下,升級成兩顆 1.5TB HDD (RAID1)。更換硬碟是小事,不過這個硬碟上有些服務,如網站,資料庫,還有一些重要資料,跟分享資料夾,想一想要更換也是挺囉唆的...。

想了幾個辦法,不過都不符合我既懶又挑毛病的個性... 原本考慮的更換方式有:

  1. 硬上... 新硬碟裝上去,檔案COPY過去,能停的服務就停掉,花一堆時間搬資料,然後再恢復原服務 (如 SQL、IIS等等)。至於比較麻煩的,像是目錄分享的,只好移掉再重建。中斷服務的時間就是從關機裝硬碟,一直到全部完成為止。
  2. 用 disk clone 的工具,如 true image / ghost 之類的軟體。不過這樣通常得停機做 clone, 750GB 也是要執行好一段時間,加上我這次買的是 Advanced Format 的硬碟,用這類工具會有效能的問題,事後還得校正回來… ouch, 算了...

想來想去,我用的是 windows server, 有內建的 Mirror set, 就拿來用一用好了。我真正作的是把 mirror set 的兩顆都升級,不過為了簡化說明,我底下的例子就只以替換一般的硬碟就好,反正道理是一樣的。

說穿了不值錢,就是用 mirror 的磁碟複製特性,加上 extend volume 的功能,我除了需要關機裝上新硬碟之外,其它包含資料複製的所有時間,原服務都不用中斷 (當然速度會慢一點),所有服務的設定也都不用修改,算是既無腦又防呆的完美方案... 只要簡單的按幾下滑鼠就可以達成我的目標。

 

直接來看看怎麼作的吧! 很簡單,先利用 mirror 把資料轉移到新硬碟... 然後中斷 mirror, 再用 extend volume 把磁區大小調大就可以收工了。來看分解步驟:

 

  1. 我原本的磁碟組態是長這個樣子 (圖我是事後用 VM 模擬的),其中 Disk 1 (8.00GB) 就是我要換掉的...
    image 


  2. 關機裝上一顆新的硬碟 Disk 2 之後,變成這樣 (Disk 2 (16.00GB) 是新的硬碟):
    image


  3. 把 disk1 / disk2 做成磁碟鏡像之後,就變成這樣:
     image


  4. 鏡像做好,Resync 完成後,就可以中斷鏡像了。中斷之後變成這個樣子:
    image 


  5. 目前為止,看來磁碟轉移已經完成了,剩下就是想辦法把後面的空間吃進來。接下來的就用 Extend Volume 括大 D: 的大小:
    image 
    image

 

之後就大功告成了。這方法不但簡單,而且整個過程中,全程 D:\ 都可以正常的使用。除了 (1) --> (2) 需要關機裝硬碟之外, (2) ~ (5) 全程,放在 D:\ 的 SQL DB,IIS 網站,還有 pagefile 通通都正常運作中。有了 windows server 的磁碟陣列還真是好用啊 :D

不過,事情也是有黑暗面的... 這個方法是有幾個小缺點啦...

  1. 被迫使用 "動態磁碟" :
    dynamic disk 其實不是什麼缺點啦,不過你要是會用到其它 OS,像 linux 之類的,或是用其它的磁碟管理軟體,可能就不認得了。這是缺點之一..
  2. 只有 windows server 可以使用:
    desktop os (windows 2000 pro, xp, vista, win7) 都只支援部份的磁碟管理功能,這作法關鍵的 mirror 是不支援的... 只能乾瞪眼 @@
  3. Extend Volume 只適用 windows 2008 以上的版本:
    記得 windows 2003 只支援到 span volume, 在 disk manager 裡還是會顯示兩個 partition, 只不過只會有一個磁碟機代號,容量會是兩個加起來的而已。一樣啦,是不會有什麼大問題,看起來不大爽而已 XD

偶爾換個口味,貼些小品文章,這邊我也不是很專業,有啥更好的作法也歡迎留 comment 啊 :D



2/19/2010 4:05:21 AM

升級到 BlogEngine.NET 1.6.0.0 了!

543 | BlogEngine.NET Facebook Share

1.6.0.0 出來一陣子了,不過到過年才有空升級... 主要的原因只有一個,就是最近 spam comments 實在太多了 =_=,新版對於這類問題的處理比較像樣一點..

其它改進還有 nested comments 跟其它一堆改進,就不一一列出來了,有興趣的人可到官方網站去看看。

試了一下,升級後沒啥大問題,除了 CSS 有點走樣之外... 如果各位有發現什麼地方漏掉了,請再通知我 :D

祝大家新年快樂 :D



12/19/2009 11:47:05 PM

[設計案例] 清除Cache物件 #2. Create Custom CacheDependency

543 | C# | CS | Microsoft.NET | MSDN | 小技巧 | 技術隨筆 | 物件導向 Facebook Share

上一篇廢話了這麼多,其實重點只有一個,我這次打算利用 CacheDependency 的機制,只要一聲令下,我想移除的 cache item 就會因為 CacheDependency 的關係自動失效,而不用很辛苦的拿著 cache key 一個一個移除。

我的想法是用 tags 的概念,建立起一套靠某個 tag 就能對應到一組 cache item,然後將它移除。開始之前先來想像一下 code 寫好長什麼樣子:

透過 tags 來控制 cache items 的範例程式[copy code]
   1:      static void Main(string[] args)
   2:      {
   3:          string[] urls = new string[] {
   4:              "http://columns.chicken-house.net/",
   5:              // 共 50 組網址... 略
   6:          };
   7:          foreach (string url in urls)
   8:          {
   9:              DownloadData(new Uri(url));
  10:          }
  11:          Console.ReadLine();
  12:          TaggingCacheDependency.DependencyDispose("funp.com");
  13:          Console.ReadLine();
  14:      }
  15:      private static void Info(string key, object value, CacheItemRemovedReason reason)
  16:      {
  17:              Console.WriteLine("Remove: {0}", key);
  18:      }
  19:      private static byte[] DownloadData(Uri sourceURL)
  20:      {
  21:          byte[] buffer = (byte[])HttpRuntime.Cache[sourceURL.ToString()];
  22:          if (buffer == null)
  23:          {
  24:              // 直接到指定網址下載。略...
  25:              buffer = null;
  26:              HttpRuntime.Cache.Add(
  27:                  sourceURL.ToString(),
  28:                  buffer,
  29:                  new TaggingCacheDependency(sourceURL.Host, sourceURL.Scheme),
  30:                  Cache.NoAbsoluteExpiration,
  31:                  TimeSpan.FromSeconds(600),
  32:                  CacheItemPriority.NotRemovable,
  33:                  Info);
  34:          }
  35:          return buffer;
  36:      }
  37:  }

 

 

這段 sample code 做的事很簡單,程式準備了 50 個網址清單,用 for-loop 一個一個下載。下載的 method: DownloadData(Uri sourceURL) 會先檢查 cache 是否已經有資料,沒有才真正下載 (不過下載的細節不是本篇要講的,所以就直接略過了...)。

而主程式的最後一行,則是想要把指定網站 ( funp.com ) 下載的所有資料,都從 cache 移除。為了方便觀看程式結果,我特地加上了 callback method, 當 cache item 被移除時, 會在畫面顯示資訊:

image

由執行結果來看,果然被移出 cache 的都是來在 funp.com 的網址... 接著來看看程式碼中出現的 TaggingCacheDependecny 是怎麼實作的。相關的 code 如下:

TaggingCacheDependency 的實作[copy code]
   1:  public class TaggingCacheDependency : CacheDependency
   2:  {
   3:      private static Dictionary<string, List<TaggingCacheDependency>> _lists = new Dictionary<string, List<TaggingCacheDependency>>();
   4:      public TaggingCacheDependency(params string[] tags)
   5:      {
   6:          foreach (string tag in tags)
   7:          {
   8:              if (_lists.ContainsKey(tag) == false)
   9:              {
  10:                  _lists.Add(tag, new List<TaggingCacheDependency>());
  11:              }
  12:              _lists[tag].Add(this);
  13:          }
  14:          this.SetUtcLastModified(DateTime.MinValue);
  15:          this.FinishInit();
  16:      }
  17:      public static void DependencyDispose(string tag)
  18:      {
  19:          if (_lists.ContainsKey(tag) == true)
  20:          {
  21:              foreach (TaggingCacheDependency tcd in _lists[tag])
  22:              {
  23:                  tcd.NotifyDependencyChanged(null, EventArgs.Empty);
  24:              }
  25:              _lists[tag].Clear();
  26:              _lists.Remove(tag);
  27:          }
  28:      }
  29:  }

 

30行不到... 其實程式很簡單,TaggingCacheDependency 繼承自 CacheDependency, 額外宣告一個靜態的 Dictionary<string, List<TaggingCacheDependency>> 來處理各個標簽及 TaggingCacheDependency 的關係,剩下的就沒什麼了。呼叫 DependencyDispose( ) 就可以通知 .NET Cache 機制,將相關的 cache item 移除。

用法很簡單,當你要把任何物件放進 cache 時,只要用 TaggingCacheDependency 物件來標示它的 tag:

把物件加進 Cache, 配上 TaggingCacheDependency ...[copy code]
   1:  HttpRuntime.Cache.Add(
   2:      sourceURL.ToString(),
   3:      buffer,
   4:      new TaggingCacheDependency(sourceURL.Host, sourceURL.Scheme),
   5:      Cache.NoAbsoluteExpiration,
   6:      TimeSpan.FromSeconds(600),
   7:      CacheItemPriority.NotRemovable,
   8:      Info);

在這個例子裡 (line 4), 直接在 TaggingCacheDependency 物件的 constructor 上直接標上 tags, 在此例是直接把網址的 hostname, scheme 兩個部份當作 tag, 未來就可以依照這兩種資訊直接讓 cache 裡的相關物件失效。

而要下令讓 Cache 內有標上某個 tag 的 cache item 失效,只要這行:

 

將標為 "funp.com" 的 cache item 設為失效的 cache item[copy code]
   1:  TaggingCacheDependency.DependencyDispose("funp.com");

 

結果就會如同上面的程式範例一樣,還留在 cache 的該網址下載資料,在這一瞬間通通都會被清掉...

 

用這種方式,是不是比拿到 key 再去呼叫 Cache.Remove( key ) 的方式簡單多了呢? 同時也能夠更快速的處理複雜的移除機制。其實運用 tagging 的方式只是一例,需要的話你也可以設計合適的 CacheDependency 類別。

以下是本篇文章的兩個附加參考檔案:

Download File - URL清單



11/22/2009 2:20:00 AM

終於突破單日 100KM 了 :D (台北 <--> 大溪)

543 | 安德魯的當年勇 Facebook Share

自從上個月,在露拍買了台二手車 (GIANT YUKON) 後,總算脫離每次都搭捷運租車來騎的日子了... 其實租車也沒什麼不好,不過租來的車子一來每次租的都不大一樣,二來都不能裝些慣用的配件... 騎起來總是不大順手,三來每次都得還車,行程多少會受限...

買了車後,可以騎的路線就廣多了.. 上上週搭捷運,試騎了 [捷運永寧站] <----> [大溪] 的路線,還蠻好騎的,風景也棒,這次就決定從家裡出發,拼一拼從大溪來回,順便突破單日破百..

image

圖一: 永寧 <--> 大溪 GPS Log, 來回約 50km。感謝小熊子贊助 GPS Logger :D

沒錯,男人就是這麼愛面子,自從上回有人留了話之後,有沒有單日破百,心裡總是覺的怪怪的... 加上沒趁機會累積一些當年勇,以後那有當年勇可以掛在嘴邊? 趁著這次小孩回娘家,天氣又正好,就來試一下...

image

[4.5km] 台北市淡水河三號水門 (延平門),我家到台北市河濱道,就走這裡最近了..

因為這次拼 100km, 路上老停下來照相大概就騎不完了,加上我的 G9 被帶回外婆家了,這次用的是我老爸的相機... 不大順手就沒照太多了... 中間這段就沒特別照了。所以有些地點的照片就直接用上回拍的... 上次是大晴天,這次是陰天,一看就知道了 :D

路線大概是這樣,到淡水河邊後,沿著淡水河右岸往南走,到華江橋牽車過橋,再往南沿著大漢溪右岸到新海橋,牽車上橋到左岸後一路騎到鶯歌..

 

image

[19.3km] 大漢溪左岸,快到城林橋了。

 

image

(上回的舊照片) 剛過城林橋..

 

image

[27km] 轉眼間已經到鶯歌了..

 

image

[28km] (上回舊照片) 鶯歌陶瓷博物館,經過了好幾次,可是都沒進去過 @_@

image

[28.3 km] 三鶯大橋下,鶯歌到大溪的自行車道 (2009/07 才通車)

image

過了三鶯大橋後,腳踏車道的風景就完全不一樣了 (Y)

image

過了個閘門,原來水都被關在這裡,難怪一路上大漢溪都沒什麼水...

image

真棒的風景,我喜歡這種有有山有水的 (Y)

 

image

離開溪邊,到大溪的這段路變成鄉間小逕,兩旁都是韭菜田及稻田...

image

[41.2km] 到大溪橋了 (這次沒照照片,拿上次的照片充數... :P)

看看碼錶,才 41.2km, 騎回家大概連 90 都不到,更別提破百了,真是失算 @@ 看看時間還夠,回程就繞去三峽老街逛逛好了。

 

image

雖然公司有好幾個人住三峽,不過每個都不知道那家有名的牛角麵包是那一家… 只會跟我講我上次買錯家了 XD...

看來還是 GOOGLE + 路人比較可靠,這次就找到了... 是在條不起眼的巷子裡,一家叫 [福美軒] 的麵包店.. 一堆人等著麵包出爐,排隊排到店外面... 足足排了一小時才買到 =_=,一個 20 元,每人限購 30 個...

image

在回程的路上拿了兩個出來吃,果然好吃 (Y)

 

image

柑園橋旁的XX公園 (抱歉,名字忘記了),有一片草地,前面的是蓮花池... 不過季節不對,看不到蓮花 @@ 那堆綠綠的是布袋蓮,可不是草地... 踩下去是會掉下去的

 

 

 

 

 

 

 

 

 

image

[85km] 華江橋上照的..

 

image

最後回到家,最後一個巷子口看了一下碼錶,98.5km…. @@

單日破百的行程怎麼可以敗在這最後這區區 1.5km ? 於是就繞了點路,去附近的腳踏車店打個氣,然後再回家... 正好 100.29 km! 哇哈哈,單日破百的成就達成!



9/24/2009 2:42:00 AM

[設計案例] 生命遊戲 #5, 中場休息

543 | CS | Microsoft.NET | 技術隨筆 | 物件導向 Facebook Share

在繼續下去之前,先來講一下,我希望讓這個 "生命遊戲" 程式,發展到什麼程度吧。其實前面四篇都還只是基礎入門,跟準備動作而已,接下來才開始會有些有趣的。

我希望這系列文章寫完後,這個程式要能扮演一個真正可運作的 Matrix … 沒錯,就是像電影駭客任務裡的母體一樣,這個程式會變成 Matrix 的主要架構,而各式各樣的 "生物" 可以在這個虛擬世界裡生活。為了讓它真的跑的動,所以前幾篇提到的效能問題,執行緒問題,就不能不考慮。為了讓這個虛擬世界能更擬真一點,它至少要是個依時間驅動的模式,而不是像 "生命遊戲" 最早定義的回合制,因此這個問題也要在基本架構裡解決掉。

這些 "生物" 那裡來? 當然是大家來開發 :D 最終目標是要把這 Matrix 建起來,讓各位的生物可以放進來互相較勁一番... 因此先替這些程式 (生物) 抽像化,定義好它跟世界,及跟其它生物之間互動的規格 (就是下篇 "抽像化 / 多型" 要說的) 就是必要的工作之一了。替生命定義好抽像化介面之後,就可以開始衍生出各種不同的生命型態,一起加入這個虛擬世界,因此繼承、多型的技術就派上用場了。

生命是會演化的,當世界上真的演化出一種新的生命型態時,整個世界可以 "安裝" 好新的生命型態,然後全部存檔,重新啟動嗎? 當然不行... 因此如何 "動態" 的加入新的生命型態,如何不停止 GameHost 的前提下,由新的 Assembly 載入 Class (再下篇要說明的 "動態載入"),也是必需克服的技術之一。

這幾個階段及目標,就是我這一系列文章想要做到的。聽起來好像很有趣,可是卻又沒什麼實際的用途... Orz, 沒辦法,我就是喜歡寫這類要動點腦筋的程式,即使畫面一點都不炫也沒關係... 平常工作就不大有機會寫這種程式了,加上現在又只剩一張嘴...。

如果順利的發展,我倒是有個打算,這個 GameHost 成形之後,我打算定些基本的規則,比如土地上會有一定的機率及規則,長出草 (食物) 來。而這世界有各種不同的生物 (EX: 羊),需要靠這世界上的資源維持生命。到時大家可以把自己創造的 "羊" 一起放到這個世界內,看看執行了一陣子之後,誰設計的 "品種" 比較好,最後可以一代一代的繁衍下來...。

想的很美好,不過我不像 darkthread 可以替最後優勝的造物者提供獎品.. Orz.. 未來的設計藍圖就先規劃到這裡。在繼續下去之前,我把程式重新整理了一下,有興趣的人可以下載回去。這份程式碼跟 #4 的功能結構是一樣的,只不過整個架構都作過重整,變數等命名也調整過了,是為了往後說明相關物件技術時,不會被這些從 #1 ~ #4 改的支離破碎的程式碼干擾...

嗯,講了一堆廢話,結論就是: 敬請期待續集 :D  哈哈...

下載重整過的程式碼:



9/19/2009 3:58:22 PM

[設計案例] 生命遊戲 #4, 有效率的使用執行緒

Microsoft.NET | 543 | C# | Threading | 我的作品 | 技術隨筆 | 物件導向 Facebook Share

原本這篇不講執行緒,要直接跳到 OOP 多型的應用... 不過看一看 #3 自己寫的程式,實在有點看不下去... 30x30 的大小,程式跑起來就看到 903 條執行緒在那邊跑... 而看一下 CPU usage, 只有 5% 不到... 這實在不是很好看的實作範例,如果這是線上遊戲的 SERVER 程式,裡面的每個人,每個怪物等等都用一條專用的執行緒在控制他的行為的話,我看這遊戲不用太多人玩,SERVER 就掛掉了吧! 因此要繼續更貼近實際的生命模擬遊戲前,我們先來解決效能的問題,所以多安插了這篇進來 :D

前一篇 (#3) 的主題是把生命的進行,從被動的在固定時間被喚醒 (callback) 的作法,改成主動的在指定時間執行 (execute)。 想也知道,現實世界的生物都是 "主動" 的,後面的作法比較符合 OOP 的 "模擬世界,加以處理" 的精神。但是,一個小程式就吃掉 900 條執行緒,是有點過頭了。不知道還有沒有人記得,我騙到獎品的這個程式... 很另類的用 yield return 來解決類似問題的作法... 藉著 compiler 很雞婆的把單一流程翻成數段可以切開執行的邏輯...,正好拿來利用一下,替我們把一連串連續的邏輯切段,以便利用多執行緒來處理。我的想法是這樣,原程式是用個迴圈,作完該作的事,就休息 (sleep) 一段時間。而新的寫法,我打算用 yield return new TimeSpan(…) 來取代 Thread.Sleep(…)。每個 Cell內部的程式結構修改不大,不過對於 GameHost 就是個挑戰了... 來看看修改前及修改後的程式碼:

用 yield return TimeSpan 來取代 Thread.Sleep( ) 的作法[copy code]
   1:  // 修改前
   2:  // 使用 Thread.Sleep( ) 來控制時間
   3:  public void WholeLife(object state)
   4:  {
   5:      int generation = (int)state;
   6:      for (int index = 0; index < generation; index++)
   7:      {
   8:          this.OnNextStateChange();
   9:          Thread.Sleep(_rnd.Next(950, 1050));
  10:      }
  11:  }
  12:  //
  13:  //
  14:  //
  15:  // 修改後
  16:  // 使用 yield return new TimeSpan( ) 來控制時間
  17:  public IEnumerable<TimeSpan> WholeLife(object state)
  18:  {
  19:      int generation = (int)state;
  20:      for (int index = 0; index < generation; index++)
  21:      {
  22:          this.OnNextStateChange();
  23:          yield return TimeSpan.FromMilliseconds(_rnd.Next(950, 1050));
  24:      }
  25:      yield break;
  26:  }

 

別想的太美,只改這樣,程式是不會動的... 修改過之後,麻煩的地方會在 GameHost. 因為整個 GameHost 的邏輯都反過來了。原本是 GameHost 只要放著那九百條執行緒自生自滅,它只要不斷的刷新畫面就好了。現在它則得用 foreach(…) 去詢問:

"大爺,這次您要休息多久?"

接到 yield return 傳回的 TimeSpan 物件 (代表它要休息多久後,繼續下一個動作) 後,經過這段時間,GameHost 就要再去叫醒 cell, 然後再詢問一次:

"大爺,這次您要休息多久?"

關鍵就在於 GameHost 如何能透過少量的 thread 來伺後這些大爺,而不是像 #3 的程式一樣,每個大爺都用一條專屬的 thread… 要共用執行緒,就要先想辦法把工作切碎,這是基本法則。如果你希望你的生命遊戲程式不只是作業的話,那麼效能跟即時回應的問題是必需要考慮的。在動手改寫 GameHost 程式之前,先來分析一下改寫的目標有那些:

目標是要達到像 #2 範例一樣的效果,但是要用更有效率的方式。

目標很清楚,再來就看看有什麼手段可以用了。第一個是過量的執行緒,應該要想辦法改用執行緒集區。因為 #2 用了高達 900 條執行緒,不過整體 CPU USAGE 不到 5%,大部份的執行緒都在閒置狀態。如果能想辦法把這些運算丟到執行緒集區,由集區動態管理會有效率的多。

第二,就是把原本的 Thread.Sleep(ts) 改成 yield return ts 後,原本每個 thread 自己睡覺的機制,就要改成 cell 各自回報 game host 它想要睡多久,然後由 game host 統一在時間到時叫醒它。由於一次有多個 cell 同時在運作,因此我們需要一個簡單的排程器,作法像這樣:

  1. 建立一個時間表,依照時間順序,把每個 cell 預計要被叫醒的時間標上去。
  2. 時間到了之後,就去呼叫該 cell 的 OnNextStateChangeEx(),同時取得該 cell 下次要喚醒的時間,再標到時間表上
  3. GameHost就不斷的替每個 cell 重複 (1) (2) 的動作..
  4. 同時另外用一條獨立的執行緒,作畫面更新的動作。

嗯,要處理的方式越來越清楚了。剩下的是 "時間表" 要用什麼型式來表現? 我的選擇是,我希望它是個 ToDo List, 會幫我排好時間,我只要把工作標上時間,丟進 ToDo List, 然後 ToDo List 只要能忠實的回報給我還有沒有排定的工作? 如果有,下一個要處理的工作是那一個? 什麼時後處理?

它的用法只有丟工作進去,跟拿工作出來,因此我設計它的公開介面長這個樣子:

CellToDoList 的類別設計[copy code]
   1:  public class CellToDoList
   2:  {
   3:      public void AddCell(Cell cell) {...}
   4:      public Cell GetNextCell() {...}
   5:      public Cell CheckNextCell() {...}
   6:      public int Count {get;}
   7:  }

 

裡面的實作,我就不多說了。我是把它當成 QUEUE 在設計,唯一的差別是,放進 QUEUE 的東西會先經過排序,因此不見得是 "First In First Out" 這種典型的貯列,而是會以 Cell 上標示的時間為準,依序 Out …。實作起來很簡單,用現成的 SortedList 當內部的儲存方式,加上基本的 lock 機制來確保它是 thread safe 的就夠了。

好,這些雞絲都準備好之後,就可以來打造我們的新版 GameHost 了。來看看 Code:

改用 ThreadPool / CellToDoList 的新版 GameHost:[copy code]
   1:  static CellToDoList _cq;
   2:  static void _YieldReturnGameHost(string[] args)
   3:  {
   4:      int worldSizeX = 30;
   5:      int worldSizeY = 30;
   6:      World realworld = new World(worldSizeX, worldSizeY);
   7:      _cq = new CellToDoList();
   8:      // init threads for each cell
   9:      for (int positionX = 0; positionX < worldSizeX; positionX++)
  10:      {
  11:          for (int positionY = 0; positionY < worldSizeY; positionY++)
  12:          {
  13:              Cell cell = realworld.GetCell(positionX, positionY);
  14:              cell.OnNextStateChangeEx();
  15:              _cq.AddCell(cell);
  16:          }
  17:      }
  18:      // 啟動定期更新畫面的執行緒
  19:      Thread t = new Thread(RefreshScreen);
  20:      t.Start(realworld);
  21:      while (_cq.Count > 0)
  22:      {
  23:          Cell item = _cq.GetNextCell();
  24:          if (item.NextWakeUpTime > DateTime.Now)
  25:          {
  26:              // 時間還沒到,發呆一下等到時間到為止
  27:              Thread.Sleep(item.NextWakeUpTime - DateTime.Now);
  28:          }
  29:          ThreadPool.QueueUserWorkItem(RunCellNextStateChange, item);
  30:      }
  31:  }
  32:  private static void RunCellNextStateChange(object state)
  33:  {
  34:      Cell item = state as Cell;
  35:      TimeSpan? ts = item.OnNextStateChangeEx();
  36:      if (ts != null) _cq.AddCell(item);
  37:  }
  38:  private static void RefreshScreen(object state)
  39:  {
  40:      while (true)
  41:      {
  42:          Thread.Sleep(500);
  43:          (state as World).ShowMaps("");
  44:      }
  45:  }

 

GameHost 的工作很明確,一開始 (line 18 ~ 20) 就把更新畫面的動作完全交給另一個執行緒,之後就專心處理 ToDoList 內的工作了。

接著後面的 while loop (line 21 ~ 30) 則是很單純的從 ToDoList 裡取出下一個要要動作的 Cell, 如果時間還沒到就 Sleep 等一下它。執行完後會再詢問下一次是什麼時後,同時再把他加到 ToDoList 內等待下一次輪到他時繼續。

這次的程式我沒有設定停止的條件,因此你會看到程式會不斷的執行下去。程式執行起來,結果跟 #3 沒什麼不同,畫面上的每個細胞會照著題目的規則生長或死亡,不同的是 #3 的 Game Host 需要用到 903 條執行緒,而這版的 Game Host 只要 9 條執行緒...

image

 

其實,以這樣的範例題,我大可以不用顧慮到效能的問題,不過就是示範程式怎麼寫嘛。不過,我的目標如果只是訂在怎麼寫這練習題,大可以 GOOGLE 一下就有一堆作業解答了 :D。我的目標是要展示一下,該如何開發這樣的 GameHost ? 這樣的程式,是大部份的遊戲的基礎,尤其是像線上遊戲或是 facebook 這類互動遊戲的基礎。有了像樣的 Game Host 之後,接下來就把目標放在如何建立多樣的生物,一起放在這世界裡面生活了。接下來就會大量運用到 OOP 的特點 (對,就是上一篇預告的...) 繼承及多型。

有沒有人覺的,這種程式越寫越像 Matrix (就是駭客任務裡的 "母體") 了? 裡面活著的東西其實都在我的掌控之下... =_= 哈哈... 未完待續,請期待續集 :D。

--
範例程式:



8/16/2009 2:43:27 AM

HVRemote (Hyper-V Remote Management Configuration Utility)

543 | MSDN | 小技巧 Facebook Share

被這東西搞了半天,過了幾個月後發現有善心人事寫了個工具,今天看到了特地來記一篇... 免的以後又忘了 @@

話說 Microsoft 從幾年前開始被一堆 security 的問題苦惱後,決定所有產品都把安全視為第一優先... 這是件好事啦,不過為了 security 問題,真的會把 MIS 及 DEV 的相關工作難度加上好幾倍... 今天這個就是一例: 在沒有 AD 環境下,如何遠端的管理 Hyper-V server ?

之前把家裡的 SERVER 升級到 Windows Server 2008 + Hyper-V, PC 升級為 Vista 後,當然很高興的抓了 Hyper-V 的遠端管理工具回來裝。想說大概跟以往的 MMC 一樣,輸入 SERVER 的資訊,帳號密碼打一打,就可以用了...

事情當然沒這麼簡單,不然就沒這篇了... 直接使用的結果當然只是丟個沒權限之類的訊息。GOOGLE 找了一下解決方式... 找到這文章 (有五篇,別以為很辛苦的把它照作就結束了,還有 part 2 ~ part 5 @@):

http://blogs.technet.com/jhoward/archive/2008/03/28/part-1-hyper-v-remote-management-you-do-not-have-the-requested-permission-to-complete-this-task-contact-the-administrator-of-the-authorization-policy-for-the-computer-computername.aspx

 

細節就不講了,要調整的步驟還真它X的多... 先在 CLIENT / SERVER 都建好帳號,防火牆要允許 WMI,DCOM... 再設定 WMI 相關的權限給指定的帳號,還有後續一堆安全相關的設定要開... 最後搞了半天,真的成功了,不過... 最近趕流行,把 Vista 換成 Windows 7... 真糟糕,這堆步驟又要來一次 @@

這次又找了一下解決方式,還是一樣有這堆設定要改,不過跟幾個月前找到的同一個 BLOG,版主真是個好人,他把他整理出來的步驟寫成了個工具: HVRemote.wsf … 沒錯,就只是個 script 而已,不過它可不簡單。先看一下它的網站:

http://blogs.technet.com/jhoward/archive/2008/11/14/configure-hyper-v-remote-management-in-seconds.aspx

http://code.msdn.microsoft.com/HVRemote

http://technet.microsoft.com/en-us/library/ee256062(WS.10).aspx

作者把上面那一大串的步驟都寫成 script 了,你只要把這 script 抓下來,放到 client / server 都執行一次,就搞定了 :D  真專業,還有一份很完整的操作說明 PDF 檔... 一定要推一下這個工具 (Y)

 

附帶提一下,Hyper-V 遠端管理工具是透過 MMC 來執行的,但是我喜歡像 Remote Desktop 那種簡單的作法,只要開個連線工具,驗證過之後就可以遠端桌面這樣... Hyper-V 也有提供這樣的工具。只要裝好管理工具,你的電腦就會有這檔案:

C:\Program Files\Hyper-V\vmconnect.exe

image

 

搞什麼,連登入視窗都弄的跟 Remote Desktop Client 一模一樣,有時不小心還真會弄錯 =_=

開起來後就是大家熟悉的 Hyper-V 遠端管理的畫面了。這工具只是讓你省掉從 MMC 去 connect VM 這些步驟而以,像 RDP 一樣開了就能用:

image

 

當然透過這工具,上面那堆設定步驟也要照做才會通啦,只是順帶提一下這個 tips 而已。有了 HVRemote 這工具,要設定遠端管理 Hyper-V VM 就更輕鬆了,有需要的人就參考看看吧!



8/5/2009 2:30:49 AM

JPEG XR (就是 Microsoft HD Photo 啦) 已經是 ISO 正式標準了...

Microsoft.NET | 543 | WPF | 技術隨筆 Facebook Share

先寫在前面,這篇不是什麼技術的探討或是評論,純脆是我個人看到這消息的想法而已。很久沒貼些軟體相關的文章了,最近比較少在動手寫 Code, 自然就沒什麼新題材好寫 @@,不過這兩天倒是看到一個蠻令人興奮的新聞,就是:

 

JPEG XR 已經正式通過 ISO 標準了!!

 

http://jpeg.org/newsrel26.html
http://blogs.msdn.com/billcrow/archive/2009/07/29/jpeg-xr-is-now-an-international-standard.aspx

 

JPEG 應該已經無人不知,無人不曉了吧? 不過當年還是有朋友鬧過笑話... 曾有人正經八百的來問我

"什麼是 [結合照片專業群組] 啊???" 就是 JPEG 啦 (無聊的話看一下底下的題外話)

我還丈二金剛摸不著頭腦,把他在看的整篇文章拿過來看,才晃然大悟他到底在問啥 =_= ... 原來是 "JPEG: Joint Photographic Experts Group”的縮寫... 當然類似的 MPEG (Moving Picture Experts Group) 也碰過類似的笑話... 無聊 GOOGLE 一下,竟然還查的到一篇範例...

http://support.microsoft.com/default.aspx/kb/235928/zh-tw

My God… 這翻譯真是比之前碰到了 "註冊傑克" 還絕 XD...

之前其實沒特別注意這些標準,曾經有印像的就是用 wavelet 壓縮方式的 JPEG2000... 嘗試取代 JPEG,也取得 ISO 的標準化,不過一直沒達成它的目的,只在特定領域還有應用空間。兩年前 Microsoft 隨著 Vista / WPF 推出 Windows Media Photo 的格式,後來為了讓它成為標準,換了個叫沒有 MS 色彩的名字: HD Photo, 最後變成現在的 JPEG XR ..

我是在兩年前,隨著 .NET 3.0 推出 WPF,剛好自己用的 CANON 相機的 RAW FILE 又被 WPF 支援,所以開始研究相關的 API 及 support .. 在關於 HD Photo 眾多報導中,有個觀點是我相當認同的。找不到較具代表性的消息來源,我就憑記憶寫一下,大意是:

 

隨著技術進步,未來影像設備 (如印表機,掃描器,顯示器等等) 的色彩表現能力及色域會遠超過 JPEG 格式的範圍 (現在就是了),因此儲存格式支援的動態範圍 (dynamic range) 越高,對於影像的長期保存越重要。

 

這就是處女座的龜毛個性啊... 衝著這個看法,我從 Canon PowerShot G2 時代開始,我就試著盡量用 .CRW 格式 (CANON RAW) 來保存相片,而不是用 JPEG。後來換了 Canon PowerShot G9,正好 WPF 出來,我就開始改用保存 .CR2 檔,而另外轉一份 JPEG 檔來作一般用途 (畢竟 JPEG 還是方便的多)。不過一張照片花掉 15 ~ 20mb, 保存起來壓力還真不小 =_=

 

現在看到 JPEG XR 的標準化,正好是我要的東西啊 :D   我需要的正是個能妥善保存這些影像資料細節的方式,同時能讓我輕鬆愉快的使用,不用耽心工具支不支援,或是其它五四三等問題困擾...。這些問題對阿宅來說,一點都不困難,有一缸子的工具辦的到,不過... 如果隨變看個照片,或是要 COPY 給家人朋友看,還要動用一堆雞絲,那也太辛苦了一點... 能有個通用的標準格式及大廠背書,那是再好也不過了 :D

 

所以,接下來要做什麼? 我突然慶興我一直都有留著這幾年拍下來的 RAW file (.CRW / .CR2) 檔案... 該是替我的歸檔程式翻新的時後了,下一步是開始嘗試用 .WDP 來取代現在放兩份 RAW + JPEG 的方式...



7/21/2009 10:39:00 PM

拼了! 80公里長征... (關渡 - 鶯歌)

543 | 安德魯的當年勇 Facebook Share

自從上次騎了一次關渡到八里之後,其實後來又去騎了幾次,發現也沒想像中的困難嘛,於是這次就計劃來拼長一點的路線... 關渡到鶯歌。會挑這路線,主要是上次騎到二重疏洪道時,有個路人問我:

"請問往鶯歌要怎麼騎?

我才發現,原來可以騎到鶯歌啊... 不過當下的反應是騎到那邊腿會斷掉吧? 不過用 google maps 看了一下,單程 35 公里 (加上迷路的一段路,來回應該有 80 了)... 之前近 30 公里都拼完了,騎到鶯歌好好休息,再騎回來應該沒什麼了不起吧? 加上上次那篇有人留話嗆了一句

"是男人就要挑戰百里長征啦"

....   愛面子的男人於是就很天真的出發了... =_=

很多人問我,為什麼每次都從關渡出發? 哈哈... 原因只有一個: 我很懶 :D,因為那邊捷運出站就有租車店,一輛還可以的變速登山車,當天租一次一台只要一百塊... 想想我一個月頂多騎一次,帶車子搭捷運票價也不止這個錢... 用租的比較方便,所以每次騎的路線就都挑從關渡為起點..

不過這次騎完,開始改變想法了... 哈哈,最後面再講。先來看看這次計劃的路線:

image

有 GOOGLE Maps 真是方便... 這次路線很簡單,前 1/3 是之前騎過的,之前是繞蘆洲三重一圈就回關渡了,這次會過重新橋,就改沿著大漢溪左岸,一路騎到鶯歌鎮...

出發前上網查了查,發現很多人騎過這段,最後是參考這個人的行程,看了才知道原來鶯歌有個 [阿婆壽司] ... 特色是便宜又不錯吃。雖然我沒有特別愛吃壽司,不過就把它當個目標吧,不然拼到鶯歌我也不知道要幹嘛 XD

算了算時間,一趟算 2.5 hr, 來回 5 hr, 加上一個小時休息吃東西,嗯,不用太早出門...

(啊,大家不要學... 你會後悔的 =_=)

2009/07/19 12:25

image

過程就不多說了 :P,到了關渡捷運站後,東摸西摸,騎上車開始動身後,過了大度路就先拍一張。我懶的拿紙筆出來計了,就拿起相機拍一拍了事 (時間可以從 EXIF 查,正好省掉記錄的動作),只可惜我的 G9 不支援 GPS ... 不然連地圖都不用標了 :D  Canon 你什麼時後要出內建 GPS 的相機...

路上的便利商店,買了兩罐冰釀綠茶,加上租車店老闆送的一罐水... 就上路了...

2009/07/19 13:37

image

悠哉的騎了一個小時,到了重新橋... 橋下有跳蚤市場,真熱鬧... 好多人 :D,不過騎著車不好人擠人,就沒進去逛了..

2009/07/19 13:43

image

雖然沒進去逛,不過也是要拍張照紀念。這邊的路標示實在不怎麼清楚,到了橋下就迷路了 :D 哈哈.. 在那邊摸了十幾分鐘,地圖拿出來,還看太陽在那邊認一認方向,硬著頭皮找了對的方向就騎下去... 沒有自行車道,只好走省道,自己識相一點靠邊邊騎...

好在沒騎多遠,就找到自行車道 =_=,就一路沿著大漢溪左岸的自行車道一路往西南騎...

雖然是 "自行車道",不過實際上也只是快速道路隔一條出來給自行車專用... 經費的關係吧 @@,沒關係,有就好,標示清楚一點就好。至少這邊不會騎一段就找不到路... 這是好處 :P

2009/07/19 14:02

image

騎到一半,才發現,原來新莊的 IKEA 就在路邊耶 :D

沒想到第一次來新莊的 IKEA 是騎著自行車來... 今天沒機會進去逛逛,只好拍張照紀念一下。

2009/07/19 14:15

image

騎到一半,看到一座天橋,上去後就可以在河提上面繼續騎,或是跨過河提到另一面的巷道裡。我也忘了看誰的文章介紹,他特別介紹了天橋兩側的鐵管... 我就糊理糊塗的跟著牽上去...

(大家不要學啊,這條路是錯的 XD 請不要上橋,繼續沿著自行車道騎就好 …)

image

都走錯了,不過走過總要留個記錄,還是貼一下好了,不要小看那兩根鐵管併起來的軌道,這樣子牽單車上橋還真的很輕鬆耶 (Y)(Y)(Y),不但很輕鬆就推上去了,鐵管中間還能卡著腳踏車的輪胎,不會亂跑,連車子都不用特別去控制它的方向,只要花點力氣把車往前推就好,真是聰明的設計 (Y)

2009/07/19 14:32

image

沿著河岸騎了一段之後,自行車道到這邊就結束了... 這邊下來把車扛上河提之後,就進入最後一段 [淡水-鶯歌] 10KM 的車道...

我是不知道為什麼這段叫 [淡水-鶯歌] 啦 @@,不曉得的人還以為真的 10 KM 就到了... 總之,這 10 KM 騎完就到鶯歌了,快到了快到了...

2009/07/19 14:49

image

遠遠看到這棟建築,還以為是什麼紀念館還是啥的... 原來是個抽水站...

2009/07/19 15:05

image

路過看到的,覺的很有意思就拍一下 :D

河邊放了一堆消波塊,竟然有人把它漆成這個樣子... 哈哈,真有創意,看起來就像一堆躲在草叢裡探出頭找獵物的迅猛龍...

2009/07/19 15:37

image

一路都沒看到路標,也不知道到底還多久 @@,最後怎麼覺的那一公里好遠... 騎到這邊早就沒力氣了,都慢慢騎... 總算撐到了鶯歌。看看地圖,其實離火車站沒多遠,不過懶的過去照相了 @@,動身去找阿婆壽司..

2009/07/19 16:12

image 

一開始問了一個媽媽,說往前騎,看到中正三路左轉就到了 (最後找到的地點是中正一路右轉)...

騎錯之後又問一個廿歲左右的小妹妹... 前面路口右轉就到了 (最後是左轉才對 =_=)

路人都報錯,我怎麼還找的到? @@,一切都要感謝全家便利商店外面整面牆上畫的地圖... 哈哈,最後是靠那張地圖找到的...

停下來吃了盒壽司 + 茶碗蒸,肚子餓了什麼都好吃... 50塊就吃飽了... 順手多帶了一盒回去... 水到這邊也都喝光光,去便利商店再補兩罐...

2009/07/19 19:00

image

image

回程的路線都一樣,就不再多介紹了,當然迷路過的地方就不會再走錯了 :P

路上河邊有好多人在玩搖控飛機,看了好想也去買來玩... :D  有個傢伙好強,控制的好靈活,其它的只是在亂飛而已。看他表演了好幾招,最後還表演了高級空戰技巧 - 英麥曼迴轉 (Immelman turn) ... 可惜他秀完這招就降落了 (降落的動作也很乾淨利絡... 其它的看起來都像要墜機的樣子 XD),不然我大概會在旁邊一直看吧...

其實從鶯歌要回程起,早就沒啥力氣了 :D,回來的速度就慢多了,騎回二重已經七點了... 當天天空雲還蠻厚的,不過我鐵齒沒搽防曬油,兩隻手被曬的好痛 Q_Q ...

不過也多虧這樣的天氣,當天有晚霞耶... 趕路歸趕路,有大景還是要停下來照一下 :D

不過沒背腳架,騎的很累手也拿不穩相機 @@,只好亂拍一通... 貼幾張還可以看的相片....

 

2009/07/19 19:27

image image

騎到關渡大橋,那段上坡早就沒力了,連試著騎上來的念頭都沒有.. 當大家都往前衝時我就很沒面子的下來用牽的... 哈哈。一路上一直很納悶,怎麼那些身材嬌小的長腿正妹,每個體力都這麼好 =_= 騎在前面跟都跟不上.... 車子比較好的關係嘛 @@

牽上橋後,休息一下,相機靠著欄干拍了幾張夜景,拍完就繼續往捷運站趕路了...

2009/07/19 19:50

image 

總算... 我頭一次這麼期待進到捷運站... 哈哈,總算可以坐下來了... 不然騎一天下來,屁股還真的會痛 @@

趕在 20:00 之前,總算拼完今天的行程了 :D  哈哈... 今天這樣騎下來,才發現裝備是很重要的... 平時騎兩三個小時那種就沒差了,要騎長程一點的真的要準備一下... 喝的一次就喝掉五罐小保特瓶... 延路都沒便利商店好補貨... 手套要戴 (不然手會握的很痛),排汗衣褲 (不然流一堆汗很難過),適合的背包 (一樣... 不然背很難過),還有電池夠力的 MP3 (我買了台 iPico, 還不錯 (Y),連續聽了七個小時... 只有最後一小段路沒得聽)...

最後,挑台自己的車好像也很重要... 哈哈... 這天我動過不只一次的念頭,很想到鶯歌就扛著腳踏車,搭火車回台北... 不過想到還要回到關渡去還車,就很懶... 還是騎回去好了。看看地圖,如果有自己的車的話,搭捷運到永寧,出來就樹林那一帶了,騎一個小時左右就可以到鶯歌了吧,省了不少路 :D  不過等到有買車再試吧...

這次相片放在 facebook 上,有興趣的人過去看看吧 :D

這次在鶯歌,又有路人在鶯歌問我 "請問大溪要怎麼去" @@

嗯,再說... 這一定是巧合... 這一定是巧合...



5/13/2009 3:40:03 PM

555555 人次紀念!

543 Facebook Share

image

正好想到,開了自己的部落格來看,啊!!! 總點閱次數: 555551 ...

多按了幾下 F5,就抓到這個畫面 :D

 

沒啥特別的,單純記念一下... 這個幸運兒,只是個 Bot 啦...  T_T



5/5/2009 3:12:38 AM

關渡騎單車

543 | 安德魯的當年勇 Facebook Share

這次來寫點不一樣的,寫點休閒的吧。

 

上個月跟家裡大人跟兩個小孩,去了趟關渡騎腳踏車,騎完覺的那邊還不錯 :D,不過因為小孩狀況多,最後沒能騎到八里天就黑了,租來的腳踏車又沒燈,只好半路就折回來了。回家翻了一下地圖,發現只剩 1/3 不到的路程啊 @_@... 於是這次趁著大人帶著小孩回娘家,碰到難得的好天氣,就自己一個人帶著裝備出發去了 :D

計劃要騎的路線很簡單,一點都不困難... 就是從捷運 [關渡站],往關山公園,沿著淡水河岸的腳踏車道,騎過關渡大橋,到八里老街,再一路騎到十三行博物館。 不過計劃總是跟實際執行時不一樣 =_= ...  直接來寫流水帳吧... 照我騎的順序看下去...

這篇不是什麼專業的介紹,老實說我也是第一次騎而已,沒做什麼功課,想來參考的可能會失望吧 :D 只是單純的記下來自己留個紀錄而以。有興趣的請繼續往下看 :D

 

 

1. 捷運關渡站(15:09) ~ 八里渡船碼頭 (16:05), 共 8.4 KM

image 

GOOGLE MAPS 還蠻好用的,地圖跟路線都標的好好的 :D 上面的路線其實是車子走的,跟我真正騎的腳踏車道有點不同... 不過差不多啦,我就借用它的地圖標示一下。上面的每個點 (綠色的英文字母) 就是底下照片標的 ABC,各位可以對照著看。

第一段的路限很簡單,就是到了關渡捷運站後,租了腳踏車就上路了。從關渡捷運站出發,一路騎到八里渡船口而以.. 太陽還不小,但是天空雲也很多,還頗耽心會不會下雨... 因為這次出發帶了一堆配備 (腳架,相機,閃光燈,耳機...),就是沒帶傘 ...

 

A. 15:09,捷運關渡站 ( 0.0 km )
IMG_1086 (Canon PowerShot G9) (1024x768)

搭了半個多小時的捷運,到了關渡站,拍個照紀念一下。其實這裡沒什麼好照的,只是我也懶的拿紙筆出來記時間了... 哈哈,直接拍個照,回家可以看到照片,也看的到時間...  突然覺的應該買個有 GPS 的照相手機,這樣拍出來連座標都有了 [Y]

另外一個敗家目標是 MP3 隨身聽... 平常沒在聽,不過自己一個人邊騎車邊聽還真愜意,聽了一下午的陳綺貞... 結果聽到連手機都沒電了 =_=,看來用手機聽 MP3 不是長久之計,有空來物色一台...

好,列入敗家清單內... 出了捷運站就有租腳踏車的地方。雖然看其它網站,都是說要步行 15 分鐘到關渡宮那邊再租車,不過... 實在是懶的多走這 15 分鐘的路程了,在門口的租車店就租下去,到晚上八點,一次一百...

 

 

 

B. 15:16,大度路 (0.3km)
IMG_1090 (Canon PowerShot G9) (768x1024)

騎出巷子口,穿過橋下就到大度路口了,單純覺的這個景跟這個建築,好像在國外的感覺,就順手拍了一張...

 

 

 

C. 15:26,關渡棧橋碼頭 (1.1km)
IMG_1091 (Canon PowerShot G9) (1024x768) IMG_1092 (Canon PowerShot G9) (1024x768)

騎著腳踏車還蠻快的,穿過巷子,經過關渡醫院,到了關度宮,就到 [關渡棧橋碼頭] 了... 十分鐘不到的車程而已。在這邊看到天氣有點陰陰的,遠方已經看的到等等要過去的關渡大橋...

 

 

D. 15:32,關渡大橋上 (2.5km)
IMG_1095 (Canon PowerShot G9) (1024x768)

騎起來好像真的沒什麼挑戰 @_@,跟上次載著小孩完全不一樣,一方面親子車本來就不好騎,另一方面自己騎也比較自在一點,不到十分鐘已經在關渡大橋上了... 上橋是吃力了一點,人也多,只好下車用牽的... 這張照片就是在橋邊的步道拍的... 再往前左轉就在橋上了..

 

 

E. 15:56,已經到對岸了 (5.5km)
IMG_1100 (Canon PowerShot G9) (1024x768) IMG_1104 (Canon PowerShot G9) (1024x768)
過到對岸後,沿著河畔騎了廿分鐘左右,這個點不知道叫啥名字,會停下來只是喝帶來的冰釀綠茶,也剛好看到有個左右相反的 [八里左岸] 石碑,停下來照個相記錄一下... 不過有對姊妹 (應該是姊妹吧) 抱著狗一直在那邊照... 哈哈,心裡噓了半天還是不肯走... 還一直猛拍,一直拍,一直拍.... =_=,我又不想跟她們慢慢耗...,就讓妳們倆跟愛犬免費登上我的部落格一次吧 =_= ...

 

騎到這邊已經快到八里渡船頭了,沒有想像中的久嘛... 上次竟然騎不到 @_@

 

 

F. 16:05,八里渡船頭 (8.4km)
IMG_1106 (Canon PowerShot G9) (1024x768) IMG_1107 (Canon PowerShot G9) (1024x768)

再往前騎不到十分鐘,就... 到.. 了 @_@,從租到車到騎到八里,也才五十分鐘左右,扣掉停下來喝個水,照個相的時間... Hmm… 果然是很休閒的路線...

這裡一樣一堆人,趕不走 (我也沒那個膽.. 哈哈),就照進去了 :D

這邊是八里渡船頭,可以直接搭渡輪到淡水碼頭... 就是有顆大蓉樹那邊,還有一堆阿給小吃店的地方。

 

 

 

IMG_1109 (Canon PowerShot G9) (1024x768) IMG_1112 (Canon PowerShot G9) (1024x768)

照完相想繼續往下騎,一路直攻十三行博物館... 不過… My God! 那來這麼多人... 我最討厭人擠人了 :@,看到一堆人就很沒力... 何況牽著車跟本動彈不得... 就放棄繼續往前走的念頭了。

翻了翻地圖,另一邊有生態公園,看看時間跟騎的速度,應該還很夠吧 :D,就改變計劃往回走了...

 

 

 

 

 

 

2. 八里渡船碼頭 (16:05) ~ 疏洪生態公園 (17:20),共 8.0 KM

image

在決定不想擠過人群,往十三行博物館前進之後,看了看地圖,就決定往南騎到生態公園看看,這種地方人應該不會那麼多吧 :D

這一趟的路程也差不多一樣八公里,沒騎過,反正自己一個人就騎看看 :D

 

 

 

 

B. 16:26,八里左岸石碑 (11.3km)
IMG_1114 (Canon PowerShot G9) (768x1024) IMG_1119 (Canon PowerShot G9) (1024x768)

又回來這裡了! 不過,這次那對姊妹跟狗已經不在了 :D,沒人在拍照...  終於輪到我拍了 :D

石碑旁邊原來還有說明啊... 剛才都沒看到。本來想拿起相機自拍,不過技術不好,都拍不到後面,腳架也懶的扛出來... 就算了...,繼續往下一站!

 

 

 

C. 16:38 岸邊某個休息區 (12.3km)
IMG_1122 (Canon PowerShot G9) (1024x768)

回程的途中,有一小片沙灘,正好看的到關渡大橋,就照一下紀錄時間...

 

 

 

 

 

D. 16:50,虹橋廣場 (12.9km)
IMG_1124 (Canon PowerShot G9) (1024x768) IMG_1128 (Canon PowerShot G9) (1024x768) image

已經騎回來到關渡大橋橋下了,這邊剛好是從橋上下來的自行車道的地方,原來這 SQUARE 叫 "虹橋廣場" 啊... 拍個照。

不過這次沒有要過橋了,繼續沿著左岸往南騎...

 

 

E. 17:06 獅子頭長橋 (14.5km)
IMG_1133 (Canon PowerShot G9) (1024x768) IMG_1136 (Canon PowerShot G9) (768x1024) image

這裡是個半園型的橋... 不大會講,我剛好也沒照 @_@,抓張 GOOGLE MAP 的衛星照來看看... 那個像量角器的東西,就是獅子頭長橋啦...

這邊我只停下來拍照而已,沒多休息就往下一站去了...

 

 

 

17:12 觀音坑溪橋 (15.2km)
IMG_1140 (Canon PowerShot G9) (1024x768) IMG_1141 (Canon PowerShot G9) (768x1024) image

還蠻特別的一座橋,造型不錯就照了一下,其實橋很小一座... 就貼個照片跟 GOOGLE 衛星空照圖意思一下..

 

 

 

17:20 疏洪生態公園 (16.0km)
IMG_1146 (Canon PowerShot G9) (1024x768) image

不知不覺就騎到目的的了,果然沒很多人,一邊是河一邊是草地,在這邊坐著休息還蠻舒服的... 陪我騎了半天的腳踏車,終於有機會入鏡頭了 :D,叫不出來的牌子,不過還蠻好騎的 (Y),一次一百塊,我是覺的不貴啦,自己買一台少說四五千吧? 光是帶車子搭捷運就不只這一百塊了 =_=,還是當場用租的方便...

 

 

 

 

 

 

 

3. 疏洪生態公園 (17:20) ~ 捷運關渡站 (19:00),共 6.6 KM

image

看看時間也差不多了,還得趕回去接大人跟少爺公主回家 =_=,在疏洪生態公園休息一下就回頭了。一路上的風景跟景點都介紹過,就不多提了。在回程的路上,才發現 MP3 隨身聽的重要啊 @_@,一整天聽下來,也沒幾個小時 (3HR左右),我的手機在接到大人打來的電話之後,就... 沒... 電... 了,嘖嘖,windows mobile 的手機聽個 MP3 就這麼耗電...

不過這樣一路聽聽 MP3 還真是過癮,就是這樣我才想去買台來用... 有沒有推薦的? iPod 就不用推了,我沒這麼時尚 @_@...

 

 

17:42 關渡大橋 (八里 --> 淡水,19.0 km)
IMG_1156 (Canon PowerShot G9) (1024x768) IMG_1160 (Canon PowerShot G9) (1024x768) image

又回到關渡大橋了,這次是從左岸南方的步道上橋,第一張照片是還沒過橋前照的,第二張照片則是同一個地點,拍上來的地方,就是從畫面中間一路往右邊爬上來... 第三張是 GOOGLE MAPS 的空照圖,順手放上來...

這次因為時間的關係,不能待太晚,可惜沒等到晚一點天黑,沒機會拍到關渡大橋的夜景... @_@,看來腳架是白帶了...

 

 

18:14 關渡宮前的小吃 (21.5km)
IMG_1190 (Canon PowerShot G9) (1024x768) IMG_1196 (Canon PowerShot G9) image

繼續往回騎,騎到關渡宮前面的小市集吃東西... 其實當地我也不知道有什麼特別的小吃,就點了平常愛吃的就好... 這邊的鹹鴨蛋好像還蠻出名的,上回大人有買一些,不過這次就沒買了。另外離這裡不遠的淡水很有名的鐵蛋,這邊也有... 不過 $$ 幾乎便宜了一半 (9顆50),這個吃起來比鹹鴨蛋方便 (哈哈,不用剝殼),常常買了就當零嘴吃.. =_=

關渡宮就是 GOOGLE 衛星照中間的橘色建築,隔著馬路對面 (橘色屋頂),一個正方型的建築就是個小吃攤集中的場地... 無奈當天沒啥胃口,吃了一盤蚵仔煎 (五十元) ... 一份花生糖冰淇淋 (卅五元) ... 一顆鐵蛋 (帶了一包回家,九顆 50 元) 就... 飽了 =_=  不然還有其它的東西想吃一吃...

 

 

18:24 一堆怪名字的租車店
 IMG_1191 (Canon PowerShot G9) (768x1024)IMG_1192 (Canon PowerShot G9) (1024x768)IMG_1197 (Canon PowerShot G9) (1024x768)

除了蚵仔煎是坐在裡面的位子吃之外,其它我就在路邊的椅子買了就坐下來吃,路邊不是小吃就是租車店,發現他們店名還真有創意... 哈哈,害我邊吃邊笑..

第一家叫 "租八借",虧老闆想的出來...

第二家叫 "租羅記",老闆八成姓羅吧... =_=

這邊租一次只要 80,不過搭捷運的話,大概來回得多走個卅分鐘吧,算了,我是懶人,就讓另外的店家多賺廿塊錢吧...

第三家在旁邊一點,喵喵休閒車,人氣就差多了... 哈哈,招牌還在,不過店已經收起來了,底下是掛著店面出租的紅紙... 果然名字好不好記還是有差.. @_@

 

最後 19:00 整,回到捷運站前的租車店 (22.6km)

這裡就沒再拍照了 @_@    沒想到這樣很輕鬆的騎下來,也不知不覺騎了廿幾公里... 夠高速公路從台北開到桃園了吧? 這樣看起來好像還蠻遠的.. 哈哈。自己一個人騎,聽聽 MP3 就不無聊了,騎了多遠也沒什麼感覺,很適合來放鬆的。騎單車還真不錯,有風景可以看不會無聊 (平常騎機車或開車,都不能看風景 =_=),也 "好像" 有運動到,聽起來比較健康一點... :D

 

下次再看看天氣怎樣,試試別條路線... 看了看台北縣市自行車道的介紹 (這裡有地圖PDF檔下載),其它路線有往淡水 (不過淡水去過幾次,都像八里一樣人擠人 @_@),也有往關渡自然公園看水鳥的路線 (這路途比較短,不用一個小時就到了吧)....,另外還有往三重方向,可以繞一整圈三重/蘆洲... 還會經過三和夜市... 不知道有沒有好吃的小吃? 還有不知道會不會經過很紅的爆米花店? 哈哈,順便買個兩桶回來 :D

 

雖然自己騎蠻自在的,不過有人要跟團也接受報名啦 :D  看看下次有沒有機會拼完三重蘆洲這條自行車道...



4/20/2009 3:46:36 AM

個人檔案 + 版本控制...

543 | 小技巧 | 技術隨筆 Facebook Share

自從過年時換了 SERVER 的作業系統,加上過年前 NOTEBOOK 掛掉換 X40 + SSD 之後,這幾個月都陷在東換換西調調的狀態中 @_@, 好在換了 2008 之後,有 Hyper-V 的幫忙,問題簡化不少...。不過今天要講的倒是很不起眼的小東西: SVN (Subversion)

SVN 這種版本控制系統,通常是用來作程式碼的版本管理。也對啦,除了軟體開發之外,其它場合好像也不大需要這麼複雜的版本機制。不過這類系統弄多了,平常在非軟體開發的場合,也發現其實很多時後都有檔案版本問題要處理。像是平常的文件 (WORD),簡報 (PPT) 等等,都會作好一份通用的,碰到 A 客戶就改一改拿來用,B 客戶再改一改... 這不就是 brench / merge 之類的問題嘛? 所以我一直在找這樣的 solution,看看有沒有適合一般使用的。不過到現在,也換了好幾種作法,歷年來試過的作法有好幾種:

  1. VSS (Microsoft Visual SourceSafe 5.0)

    這個有用過的人,看版本號碼就知道有多古老了… 不過真正在用是 6.0 版開始。因為工作上會用的到,就順便拿來用了。它的好處是很簡單,搞懂它的邏輯就很容易上手。架設也簡單,完全是 File Based, 不需要架設專用的 Server。不過這也是後來換掉它的原因之一。

    它的使用方式,是以嚴格的控制為主要邏輯。什麼意思? 意思是你不能隨意更改檔案,要開使改檔案之前,要先 check-out 才能開始改。這樣的邏輯就是要避免未來一連串的版本衝突 (conflict) 及合併 (merge) 帶來的問題。 以軟體開發的角度來看,這樣的作法還不錯,整個團隊的開發是值得這樣作的。不過拿來管理個人檔案的話,就太過頭了。個人檔案不大會發生 LOCK 的問題,就是我改你也改,最後存檔總會有一個人的資料被蓋掉... 不過,如果我是大老闆,有十幾個秘書在幫我打雜的話就難說了 [H]

  2. VSS (Windows Volume Shadow Copy Service)

    Visual Source Safe 用了之後,發現障礙多於它帶來的優點 (以處理一般文件而言)。主要的缺點是,VSS 透過網路 / Internet / VPN 使用的速度實在是龜到可以,雖然後來 Microsoft 推出了 LAN Boost Service (還是很慢),也另外推出了 HTTP / Web Service 的存取方式 (只能透過 Visual Studio) 速度也不快。另一個缺點是一定要先開 VSS Explorer / Visual Studio, 我不過只是想開個 WORD 檔啊...

    所以後來換了另一個角度找 solution, 就試用了 windows 2003 內建的 VSS (Volume Shadow Copy Service), 替代版本控制用的軟體。它是做在 File System 層次上的機制,用了 Copy On Write (COW.. 這是縮寫,不是在罵人...) 的方式,做版本的差異控制。因此只要把檔案放在開啟 VSS 的磁碟機,完全不用更改任何使用習慣..。

    但是太自動的東西還是不適用。這種作法主要的問題在於版本太不精確了。VSS 仰賴定期作快照 (snapshot) 來作版本的管理。定期做的快照,留下來的版本很可能是無意義的,你也無法針對特定檔案的特定版本作註記 or 回複... 另外自動的快照也無法選則那些檔案要進版本,那些要退出。總之一切全自動,沒有什麼好選的。很簡單,但是功能也很有限。

    不過即使如此,一般情況下也夠用了,操作也夠簡單,當作第二種保護機制也不錯。這個 solution 我也用了好一陣子...。

  3. TFS (Team Foundation Server)

    老實說,連一般小型軟體開發,用到 TFS 都太肥了一點,自己的檔案管理用到這個真是太離譜了... 哈哈,因此這個 solution 只是閃過念頭而已,跟本沒實際裝起來試過。用這個方案,工具會是個大問題... 用的時後得開個 Visual Studio, SERVER 還得裝一大票軟體 (IIS, TFS, SQL + Reporting, SharePoint Team Service, AD…)

  4. USB DISK + PortableApps

    其實這個算不上是個 SOLUTION,只不過順便把它列上來,待會說明用。某次無意間,同事告訴我 PortableApps.com 這個工具,它是個灌在 USB 隨身碟上面的工具 & 一些綠色軟體,有自己專用的 "開始" 選單,方便你插上隨便一台電腦,就把它當作你自己的 PC 一樣使用... 老實說還不錯用 (Y),我就試著用一陣子,把所有個人相關的資料都移到上面了。現在的工作環境有點複雜,公司一台 PC,家裡一台 PC,偶爾還需要用 notebook 去客戶那邊簡報 (咳,就是我那台只有 8GB SSD 的 X40,正好沒地方放檔案)

    用了一陣子還不錯,不過碰到的又是很常見的問題: 檔案掉了怎辦? 備份問題? 讀寫速度問題? Flash Disk 寫入次數限制問題... 不外乎常備份,每天一份 ZIP 檔,用苦力作好版本控制…


  5. USB DISK + SVN

    最後,就是現在用的方案了... 主要是補 (4) 的不足: 一般的定期 ZIP 備份就跟快照一樣,事後要追出變更其實很麻煩,每次變更想加個註解又更麻煩了。當然搭配 Visual Source Safe 這種工具,把 Working Folder 指到 USB DISK 上就可以兩全齊美了。

    不過使用便利則是另一個問題,我希望能夠找個無腦一點的工具,不需事先 check-out (lock) 的動作就可以開始編輯,改完再決定 check-in (commit) 或是 undo (revert) 的模式最好。用了 USB DISK 就是希望能拔來拔去,如果必需配合特定工具 & 要即時連上 SERVER,那就有點麻煩... 想看看,當我 USB DISK 插到 NOTEBOOK 帶到客戶那邊去,都按兩下打開 PPT 在簡報了,臨時要改幾個字,用 VSS 的話,我得關掉 PPT,打開 VSS,CHECK-OUT,打開PPT,修改...

    所以後來的首選就變成 SVN 了。SVN 因應 internet / open source project 的開發模式,採取的就跟 Microsoft 是不同的策略,就是先改再說。SVN 賭你不會多人同時編同一個檔案,就算會,也不會編同一段 code … 真的碰到就再人工處理吧。另外它支援各種不同的 protocol, 透過 internet 這種連線來使用,效能也不會很糟糕...

 

到目前為止,我用的就是 (5) USB DISK + SVN 這種 solution, 老實說越用越覺的它不錯 (Y)。SVN 我還是個新手,應該輪不到我來介紹他的特色吧 XD,不過我還是挑幾個特別的地方介紹一下,這些是我用它的主要原因啊...

  1. 操作邏輯合適

    SVN 是 CVS 的接班人,它先天就繼承了 CVS 的特性: 就是適用 open source 的開發團隊。Open Source 的開發團隊跟一般的開發團隊有什麼差別? 一般商業開發都是正職的工作,很固定且很密集的進行開發及變更,因此像 Microsoft Solution (VSS / TFS) 那種要事先 lock 的機制會比較有效率。不過 open source project 就反過來,業餘的比例比較高,而且人都散布在世界各地,如果真正用 LOCK 的機制大概會哭出來吧...。我要改的檔案被你 LOCK 住了,不過我又不知道你是誰? 除了等就沒辦法了...。

    因此 SVN 先天就是以這樣的觀點來設計: 你先改了再說,改完就 commit 。反正只要沒人跟你改同一個檔案就沒事... 如果運氣真的不好,那這個人不要跟你改同一段 code 也沒事,直接 merge 就好... 只有真的很背的時後,有人跟你改同一段,那麼後 commit 的人就要負責處理 merge 的問題。不過機率很低嘛 (沒錯,尤其是只有我自己用的時後),你可以不用管它...

    過去用 VSS 常碰到這種情況: 原本只是開個文件起來看 (READ),跟本沒想要去改它,就沒有先作 check-out 的動作了。不過看到一半發現內容有誤,想要修正時... 問題就來了。以 WORD 來說,已經開起來才去 check-out 檔案的話,WORD還是會認為檔案是唯讀的... 除非你關掉 WORD 再開啟一次才有用。不過這麼一來思緒都被打斷了...。

    當然,還是一樣,正規的開發動作還可以要求,一般的文書處理要求到這樣就有點過頭了。因此 SVN 這樣的邏輯就佔了點優勢,我最常碰到的案例就是: 要出門開會,把 USB_DISK 拔出來帶走。開會過程中 (在外面,沒有網路連線) 修修改改 PPT 的內容,回到公司後直接在 NB 或是把 USB DISK 插回 PC,再用 SVN 作 commit 的動作...。

  2. SERVER 的資訊跟著目錄

    有些工具 (像是 TFS),你的工作目錄對應到那個 SERVER,是工具在維護的 (TFS 的 workspace),這時搭配 USB DISK 可能會在不同的電腦 (可能是我的 PC,也可能是我的 NOTEBOOK,甚至是帶回家裡用)。一般把設定綁在工具上的作法就很頭痛,因為好幾台都要設成一樣的,而且 USB DISK 還有可能每次的磁碟機路逕都不大一樣...

    我用的工具是小烏龜 (TortoiseSVN),它的設定就是在每個目錄下放個 .svn / _svn 的子目錄,檔案總管按右鍵叫出 SVN 的選單後,藏在裡面的設定就自動套上來了。這種操作模式,剛好對於我的用法 (USB_DISK) 很方便...

  3. 更精確,更有效率的 "備份機制"

    現在隨身碟廠商,都很愛在商品上加一些小工具,有的有壓縮,有的有密碼保護... 不過 USB DISK 很容易掉,所以所有廠商都不會忘記附上一個備份工具。連我前面介紹的 PortableApps.com 都有附一個 ( 7-ZIP + SHELL )。不過這些備份工具都有個通病... 它就真的只是 "備份" 而已,是讓你心安的。使用時機是你自己要勤勞點,記得每天按 BACKUP。要還原回來,通常就是整支 USB DISK 的內容都還原回來了,如果你只想要還原某幾個檔,或是只要查看過去備份的某個檔,那你得點好幾下滑鼠,甚至是要把整個備份解開才看的到。

    另一個備份問題是,每次都是 FULL BACKUP ... 雖然有些工具作的比較好,有差異備份 ( PortableApps.com 就有提供 7-ZIP 的差異備份),不過不還原還好,一旦要把舊資料撈出來也是很辛苦。當然這些並不是備份工具的錯,備份本來就是作這些事。中間有落差的地方在於 USER 需要的是一個歸檔的機制啊,除了備份也需要調閱舊的版本內容。這時版本控管工具,正好就成為 USB DISK 在 PC 上的第一線 "備份資料庫" 了。當你在 check-in / commit 時,不自覺的就在版本系統內放了一份備份了,不放心的人可以再啟用像 VSS (Volume Shadow Copy Service) 或是定期壓 ZIP 這類一般的備份機制作第二層保護,就很足夠了。

    這裡的重點倒不是備份安不安全啦,而是這樣的操作方式,很自然的就會在 SVN Repository 內留下一份內容,同時也方便你替這個版本作註記,未來要調閱,甚至是比對內容差異都很容易...

  4. 異地存取

    USB DISK 雖然很方便,也可以隨身攜帶,但是我就是會常常忘掉它... 常常忘了拔就出門... 在外面如果還要存取我的 USB DISK 的內容,有網路的話,版本控制系統也很好用。我用的 SVN SERVER 是 Visual SVN,它就有個很簡易的 WEB 介面,真的忘了帶還可以連回我自己的 PC,把檔案下載回來。

    如果用的電腦有灌 SVN CLIENT,那你還可以做些基本的操作...。這套比起來就比 VSS 強的多。VSS 完全是 file system base, 透過遠端的操作必需先用網芳之類模擬 file I/O 的方式,效能很糟糕... 雖然 2003 年左右 Microsoft 替 VSS 加其了很多功能,像是 LAN Boost Service (我搞不懂它怎麼做的),或是替 VSS 加上 Web Service Interface (可以透過 HTTP),不過效果都不盡理想。

這些功能加一加,就是我現在在用的個人檔案管理方案了啦。家裡有台現成 SERVER,很多問題就更好解了。這套作法正好给有需要的人參考看看,如果你用了有什麼心得,或是有其它更好的用法也歡迎分享 :D



4/17/2009 7:51:00 PM

RUNPC 精選文章 - 運用ThreadPool發揮CPU運算能力

543 | C# | RUNPC | Threading | 我的作品 | 技術隨筆 | Microsoft.NET | [精選文章] Facebook Share

果然這個什麼東西都上網的年代,要三不五時的 GOOGLE 一下自己,才會知道那些網站把你的八卦跟內幕爆了出來... 不過應該沒啥週刊記者對我有興趣吧? 哈哈。在 GOOGLE 自己名字時,倒是意外發現,之前投稿的文章,又有一篇被拿來登在網站上的精選文章了 :D

特此留念一下 :D

http://www.runpc.com.tw/content/main_content.aspx?mgo=176&fid=E02

--

順便整理一下懶人包:

另一篇精選文章 [RUN!PC 精選文章 - 生產線模式的多執行緒應用]

過去投過的系列文章 (multi-threading programming using c#):

2008/11. 生產線模式的多執行緒應用
2008/09. 用ThreadPool發揮CPU運算能力
2008/06. SEMAPHORE在ASP.NET的應用
2008/04. 以ASP.NET開發同步WEB應用程式



1/16/2009 2:10:00 AM

RUN!PC 精選文章 - 生產線模式的多執行緒應用

543 | C# | RUNPC | Threading | 我的作品 | 技術隨筆 | Microsoft.NET | [精選文章] Facebook Share

http://www.runpc.com.tw/content/main_content.aspx?mgo=178&fid=E08

無意間 search 我自己的名字,才發現這篇文章除了投稿到 RUN! PC 之外,原來還有刊在網站上的精選文章啊...

哈哈,暗爽一下,順道貼一下 link, 讓沒看到雜誌的網友們也有機會看一看在下的作品...



12/10/2008 11:30:18 PM

XmlWellFormedWriter.WriteRaw( ) 的 Bug 後續發展

Microsoft.NET | 543 | C# | MSDN | TROUBLE SHOOTING | XML | 技術隨筆 Facebook Share

一時順手,就按下 Visual Studio 2008 上面的 [Report Bug] 回報上一篇發現的 Bug, 沒想到 M$ 真的有回應耶... :D

反正 M$ 在 connect 裡的回應本來就公開的,我就順手貼一下:

 

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=386899&wa=wsignin1.0

 

Hello,
Originally the WriteRaw method was designed for XmlWriters that are formatting text XML into a file. In those cases the WriteRaw method can be used to write out an XML fragment that is already formatted and checked for well-formedness. It can also be used for writing text nodes with special character that have been already escaped and no further processing of the text is needed.
However, when we introduced the XmlWriter over XmlDocument/XDocument (accessed via XPathNavigator editing methods), the use of the WriteRaw method on top of XmlDocument became controversial. We had two options:
1.    Threat it as a text
2.    Parse it into nodes


The second option is very difficult (if not possible) to do. The XML fragment can we written out in multiple WriteRaw calls, so we could not assume that a single WriteRaw will contain a fully enclosed fragment. It can also be interleaved with other XmlWriter calls and nested many times – overall a very hard thing to implement properly. So that is why we have decided to treat the WriteRaw content as text, which is what you are seeing.
If you have an XML fragment in a string and you want to append it to XmlDocument, you can do it like this:


                XmlDocument doc = new XmlDocument();
                XmlElement rootElement = doc.CreateElement("root");
                rootElement.InnerXml = "<a/><a/><a/><a/><a/>";
                doc.AppendChild(rootElement);


Or if you really want to use the XmlWriter from XPathNavigator.AppendChild(), you can create an XmlReader over your fragment and use the WriteNode method on the XmlWriter:


                XmlDocument doc = new XmlDocument();
                XmlWriter writer = doc.CreateNavigator().AppendChild();
                writer.WriteStartElement("root");
                using (XmlReader r = XmlReader.Create(new StringReader("<a/><a/><a/><a/><a/>"), new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment } ) ) {
                    writer.WriteNode(r, false);
                }
                writer.WriteEndElement();
                writer.Close();
                doc.Save(Console.Out);


I hope this helps.
Thank you,
-Helena Kotas, System.Xml Developer

 

看來 M$ 認為這是權宜之計,不算 BUG,要 USER 就直接避掉了,只是他建議的解法剛好就是我上一篇用的,用 XmlReader, 搭上 ConformanceLevel.Fragment 的設定解決掉...

只不過,這種情況,不是應該丟出 NotSupportException 比較好嘛? 幹嘛拿個不適當的實作填進來?

 

提外話,現在 WEB 2.0 的時代,M$ 工程師除了寫 CODE 之外,也要負責回客戶的問題了? 真辛苦...



12/7/2008 4:35:27 PM

原來 System.Xml.XmlWellFormedWriter 有 Bug ..

Microsoft.NET | 543 | C# | MSDN | TROUBLE SHOOTING | XML | 小技巧 Facebook Share

果然沒啥人知道的 code, bug 也會比較慢被抓出來 ... 兩個小時前我才貼了找到 XmlNodeWriter 的替代品,用了一下就被我挖到一個 BUG ... @_@

先來看看我的 Sample Code:

XmlTextWriter v.s. XmlWellFormedWriter[copy code]
   1:  // test xml text writer, correct result
   2:  // output: <?xml version="1.0" encoding="big5"?><root><a/><a/><a/><a/><a/></root>
   3:  {
   4:      Console.WriteLine("Using XmlTextWriter:");
   5:      XmlWriter writer = XmlWriter.Create(Console.Out);
   6:      writer.WriteStartElement("root");
   7:      writer.WriteRaw("<a/><a/><a/><a/><a/>");
   8:      writer.WriteEndElement();
   9:      writer.Flush();
  10:      Console.WriteLine();
  11:      Console.WriteLine();
  12:  }
  13:  // test xml node writer, wrong result
  14:  // output: <?xml version="1.0" encoding="big5"?><root>&lt;a/&gt;&lt;a/&gt;&lt;a/&gt;&lt;a/&gt;&lt;a/&gt;</root>
  15:  {
  16:      Console.WriteLine("Using XmlWellFormedWriter:");
  17:      XmlDocument xmldoc = new XmlDocument();
  18:      XmlWriter writer = xmldoc.CreateNavigator().AppendChild();
  19:      writer.WriteStartElement("root");
  20:      writer.WriteRaw("<a/><a/><a/><a/><a/>");
  21:      writer.WriteEndElement();
  22:      writer.Close();
  23:      xmldoc.Save(Console.Out);
  24:      Console.WriteLine();
  25:      Console.WriteLine();
  26:  }

 

而這是程式的輸出畫面:

image

 

兩段 code 除了拿到的 XmlWriter 來源不同之外,用它寫 XML DATA 的方式是一致的,不過寫出來的 XML 則完全不同。看來兩種 XmlWriter 對於 WriteRaw(...) 的實作不大相同。而照 MSDN 上的說明來說,XmlTextWriter的行為是對的,XmlWellFormedWriter 則太雞婆了,沒事多作一次編碼...

 

該說運氣好嘛? 哈哈... 繼上次撈到一個 SmtpMail 的 Bug 之後,這次又撈到一個... 要用的人注意一下,不過即使有這個 Bug, 也不會影響它的地位啦,這 Writer 解決了我很大的困擾,動搖國本也要用下去... (咳... 不過是避開一個 API ...)

 

最後我改了用法,一方面 API 有 BUG 是一回事,另一方面直接用這 API 也很危險,因為 MSDN 說它不會去做內容的驗證,也就是說透過 WriteRaw( ) 寫進不合法的資料,會讓你整份輸出都毀了... 第二個原因比較重要,因此我換了一個替代作法, 類似 Pipe 一樣,把 XmlReader 讀到的東西都寫到 XmlWriter:

XmlCopyPipe 實作[copy code]
   1:  /// <summary>
   2:   /// 從 XmlReader 複製到 XmlWriter
   3:   /// </summary>
   4:   /// <param name="reader"></param>
   5:   /// <param name="writer"></param>
   6:   private static void XmlCopyPipe(XmlReader reader, XmlWriter writer)
   7:   {
   8:       if (reader == null)
   9:       {
  10:           throw new ArgumentNullException("reader");
  11:       }
  12:       if (writer == null)
  13:       {
  14:           throw new ArgumentNullException("writer");
  15:       }
  16:       while (reader.Read() == true)
  17:       {
  18:           switch (reader.NodeType)
  19:           {
  20:               case XmlNodeType.Element:
  21:                   writer.WriteStartElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);
  22:                   writer.WriteAttributes(reader, true);
  23:                   if (reader.IsEmptyElement)
  24:                   {
  25:                       writer.WriteEndElement();
  26:                   }
  27:                   break;
  28:               case XmlNodeType.Text:
  29:                   writer.WriteString(reader.Value);
  30:                   break;
  31:               case XmlNodeType.Whitespace:
  32:               case XmlNodeType.SignificantWhitespace:
  33:                   writer.WriteWhitespace(reader.Value);
  34:                   break;
  35:               case XmlNodeType.CDATA:
  36:                   writer.WriteCData(reader.Value);
  37:                   break;
  38:               case XmlNodeType.EntityReference:
  39:                   writer.WriteEntityRef(reader.Name);
  40:                   break;
  41:               case XmlNodeType.XmlDeclaration:
  42:               case XmlNodeType.ProcessingInstruction:
  43:                   writer.WriteProcessingInstruction(reader.Name, reader.Value);
  44:                   break;
  45:               case XmlNodeType.DocumentType:
  46:                   writer.WriteDocType(reader.Name, reader.GetAttribute("PUBLIC"), reader.GetAttribute("SYSTEM"), reader.Value);
  47:                   break;
  48:               case XmlNodeType.Comment:
  49:                   writer.WriteComment(reader.Value);
  50:                   break;
  51:               case XmlNodeType.EndElement:
  52:                   writer.WriteFullEndElement();
  53:                   break;
  54:           }
  55:       }
  56:   }

 

很好用的作法,就像過去需要 COPY XML 資料,最常見的就是把來源跟目的都用 XmlDocument 載入,直接用 ImportNode( ) 把 XML 片段資料搬到另一個 XmlDocument 再儲存。跟上一篇的原因一樣,看起來很蠢... 就想到這個作法,透過 XmlReader, 拿到的是已經 parsing 過的資料,直接寫到 XmlWriter。而我用的 Writer 正好又可避開重複作 parsing 動作的優點,正好這樣效能跟可用性都兼顧了... 經過 parsing, 至少寫出來的東西會安心一點...

 

把最後我的程式搭配這個 XmlPipeCopy 改一改:

用 XmlCopyPipe 取代 WriteRaw( )[copy code]
   1:  XmlDocument xmldoc = new XmlDocument();
   2:  XmlWriter writer = xmldoc.CreateNavigator().AppendChild();
   3:  writer.WriteStartElement("root");
   4:  XmlReaderSettings settings = new XmlReaderSettings();
   5:  settings.ConformanceLevel = ConformanceLevel.Fragment;
   6:  XmlReader reader = XmlReader.Create(
   7:      new StringReader("<a/><a/><a/><a/><a/>"),
   8:      settings);
   9:  XmlCopyPipe(reader, writer);
  10:  writer.WriteEndElement();
  11:  writer.Close();
  12:  xmldoc.Save(Console.Out);

 

試了一下,果然如預期的執行了 :D,結果也沒錯,還好 XmlWellFormedWriter 的 Bug 只存在於 WriteRaw... 閃開就沒事了:

image

 

 

其中有個陷阱,就是如何用 XmlReader 讀取 XmlFragment (可以有多個 ROOT 的 XML DATA)。其實這個解法跟程式碼,大部份都是這篇看來的,只不過在裡面加了個 LOOP 跟改了名字,各位覺的好用的話記得去謝原作者 Mark Fussell, 別謝錯人了 :D



11/18/2008 1:23:00 AM

Policy Injection Application Block 小發現...

Microsoft.NET | 543 | AOP | Application Block | C# | MSDN | 小技巧 | 技術隨筆 | 物件導向 Facebook Share

因為工作的關係,最近正在研究 Enterprise Library 裡整合的 Patterns & Practices 介紹的各式 Application Block... 撇開其它的發現,有個東西一定要提一下,就是 Policy Injection ...

介紹文章我就不多說了,一樣網路一大堆,有興趣的可以看 MSDN 官方的說明。比較特別的是它的用法。當年剛開始研究 .NET 內建的 Role Based Security Control,才在讚嘆它的 code 寫起來真漂亮,只要加個 attribute, 就可以在 runtime 自動檢查呼叫時的身份是否滿足 attribute 的宣告,如下:

CAS範例程式: [copy code]
[PrincipalPermissionAttribute(SecurityAction.Demand, Role="Supervisor")]public void Foo() {    // ... }
   1:  [PrincipalPermissionAttribute(SecurityAction.Demand, Role="Supervisor")]
   2:  public void Foo() {
   3:      // ... 
   4:  }

 

不管你的 code 在那裡,只要呼叫這個 Foo method, 當時的身份 ( principal ) 如果不屬於 "Supervisor" 這個角色的話,就會引發 Security Exception... 當初看到這真是太棒了,我可以用宣告的方式來作安全控制,不需要在主程式裡加一堆囉哩叭唆的 code 來查權限...

不過當我開始研究如何 "自定" 這個行為,除了加上自己的安全機制之外,想更進一步的加上 Log 或是其它的檢查... 我才發現跟本辦不到。因為... 這行為是直接在 CLR 裡支援的啊,我可以加上一堆自定的 Attribute 掛上去,但是呼叫時完全不會觸發我的 code ...

之後研究過 AOP,發現 AOP 正是解決我這類問題的 Solution, 無奈那些 solution 都不大實際,就沒深入研究了。之後找到篇 MSDN 的文章,裡面提到 .NET Remoting 時,遠方會產生 Proxy, 同時 Client / Server 之間的溝通會介著中間傳輸層傳遞 IMessage 介面封裝的 message, 到另一端才會由 Proxy 解讀,然後用 Reflection 還原呼叫的動作... 利用 Proxy 在還原呼叫動作時,你就有機會插入你要的邏輯 (IMessageSink),做到跟上面例子類似的功能。

 

還是很不實際啊啊啊啊,我沒事也不會去用 .NET Remoting 啊,用不到的話這招對我也沒啥用 (大錯特錯!! 當年的我真是太過自信了 :~~~~) ... 這事就一直擱著了,直到...

最近在研究 Policy Injection Application Block 時,讓我看到了似曾相識的 code:

 

Policy Injection Sample Code #1[copy code]
[AuthorizationCallHandler("operation-name")]public void Deposit(decimal depositAmount){  balance += depositAmount;}
   1:  [AuthorizationCallHandler("operation-name")]
   2:  public void Deposit(decimal depositAmount)
   3:  {
   4:    balance += depositAmount;
   5:  }

 

 

這段 CODE 跟前面 CAS 的範例作用差不多,一樣是在 method 被呼叫前作一次權限的檢查。不同的是 AuthorizationCallHandlerAttribute 是自定的 (由 Security Application Block 提供的),它的作用比 ROLE 更進一階,是直接檢查授權的。之間的差別就如同 windows 大家都知道把 USER 加入 Administrators 角色的話,"預設" 就可以做大部份的事,但是你要在某個有 ACL 的物件 (如 NTFS 的檔案) 拒絕 Administrators 的存取也是可行的。前面 CAS 的例子就只是判定你是不是某角色的人,而這例子則是判定某個授權的定義允不允許你執行。

扯遠了,重點不在安全,重點是自定的 Code / Attribute 也可以這樣用啊! 由於我多年心裡的疑惑,挖出這段作法比研究 Policy Injection 更積極一點 (老闆對不起...) 哈哈,沒想到答案就在前文...

 

 

它ㄨ的!! 原來只是在 Local 使用 .NET Remoting ...

 

 

說穿了不值錢,你用的物件標上 Attribute 後,要透過它的建立方式 ( Create or Wrap ) 取得加料過的物件,再呼叫它就會有你預期的效果了。這加料過的物件,就是 System.Runtime.Remoting.Proxies.RealProxy 下的某類別啊啊啊啊... 意思是我拿這加料過的物件,就會透過 .NET Remoting 的方式去呼叫到我真正的物件,而 Policy Injection Application Block 正好就替我把我要作的動作給補上去...。

雖然心裡有被擺了一道的感覺,不過它的 code 包裝的真漂亮啊... 除了 Create 的方式由原本的 new .ctor( ) 改成它的 Create( ... ) 之外,其它就通通一樣了。更猛的是它還提供了幾個真的很實用的 CallHandler (就是呼叫時會加料的動作啦):

  • Authorization Handler
  • Caching Handler
  • Exception Handling Handler
  • Logging Handler
  • Performance Counter Handler
  • Validation Handler
  • Custom Pipeline Handlers

大部份的 Handlers 都望文生義,像是 Logging 就是呼叫時替你加一段 LOG,而 Performance Counter 則是呼叫時就替你戳一下 windows 內建的 performance counter, 讓你可以透過 performance monitor 看相關統計 (如你的 method 被呼叫過幾次... ),更神奇的是 Caching, 如果你的 method 跑的很慢,加上去之後甚至是 cache 裡已經有了上次的結果,這次呼叫就直接 return 了... (你還記得你寫過多少次資料不在 cache 內就 insert 進去的 code 嗎?) @_@

 

如果你看這篇期望看到啥 Enterprise Library / Policy Injection Application Block 的深入介紹的話,很抱歉... 我還沒那本事,哈哈... 再過陣子研究出心得,可能會寫幾篇吧...。 這類文章如果你不介意看英文的,官方的說明還有 QuickStart 的範例就夠你看了,可以參考看看,我就不獻醜了...。 這篇純粹是為了這 AB 解除了我多年來的遺憾,特地留下篇記念用的... :D



11/6/2008 1:38:00 AM

重生的 IBM ThinkPad X40 ...

543 | 敗家 Facebook Share

我自己用了快六年的 ThinkPad x31 掛掉了,又沒潑到水,送去 LENOVO 修理,就回我 "液體入侵" ... 換主機板要 NTD 26500 ... 錢太多才會修,因此就跟我姊ㄠ了她已經沒在用的 ThinkPad x40 來用用...

X40 什麼都好,就是敗在它那顆 Hitachi 1.8" HDD 效能實在太爛... 拿到 X40 後就馬上重灌 XP,剛灌好後就用 HDTune 測一下這顆硬碟的鳥效能..

HDTune_Benchmark_HITACHI_DK13FA-40B

 

後來也很巧,經過一夜灌了堆必要的軟體跟工具之後,突然喀啦一聲,硬碟就再也不能用了 :~  上網找找有無硬碟可以買? 還真慘... 都是拆機或是二手,個人保固七天或是一個月的那種。Hitachi也停產了,除了容量有 60GB 的之外也沒別的選擇了。效能很鳥的硬碟,相對的 $$ 也不算便宜,害我考慮了半天...

後來決定用 CF -> IDE 的轉卡,加上忍痛買了張 SanDisk Extreme IV 8GB CF 卡,也就是俗稱 "偽SSD" 的解決方案,裝好後好像完全換了台電腦似的,剛裝好的 XP PRO (原版光碟安裝的,沒有刪掉一堆內建的軟體跟服務),開機的 WINDOWS 光棒,跑不到一輪就進 WINDOWS 了 @_@,真是傻眼,效能的增進遠超過我的預期...

雖然容量小了點,不過效能跟原本的 1.8" HDD 實在差太多了,不足的容量就再補張 16GB SD 卡撐著用。只要不裝啥大型檔案,一般的 OFFICE 文件還不成問題,用起來也還不錯! 原本慢到想扔掉的 X40 就這樣又活了過來 :D

 

最後補上 SanDisk Extreme IV 8GB 的效能測試圖:

HDTune_Benchmark_SanDisk SDCFX4-8192



11/3/2008 2:34:00 AM

該如何學好 "寫程式" #5. 善用 TRACE / ASSERT

[精選文章] | 543 | C# | Microsoft.NET | 我的作品 | 技術隨筆 | 物件導向 Facebook Share

哈哈,這篇拖的夠久了 :P

上篇扯太多,寫到一半寫不完就留到這篇了。寫出可靠的程式,這是軟體工程師的基本要求。上篇提到了 TRACE / ASSERT 的應用,來複習一下:

TRACE: 原本是 C 的除錯用巨集,目的是用適合的方式輸出除錯用的訊息,用來跟一般的訊息輸出有所區別。因為用的是不同的方式輸出,可以很容易的統一關掉。隨著工具的進步,輸出的方式也越來越適合除錯,比如輸出到開發工具的除錯視窗,或是輸出成記錄檔等等。

ASSERT: 也是除錯用巨集,它接受一個 bool 參數,輸入值為 TRUE 時一切正常,就像沒呼叫一樣,輸入 FALSE 則會中斷程式,或是輸出顯目的警告訊息。目的在於確保程式的每個步驟情況都如預料般的順利。

這兩個東西從 C 的巨集,衍生出各種語言及環境都有各自的版本。它的目的很簡單,就是 [Writing Solid Code] 裡提到的:

用同一套程式碼,同時維護兩個版本 (RELEASE / DEBUG),讓錯誤自動跑出來

 

雖然這本書提到了不少技巧,正確的應用 TRACE / ASSERT 是最基本的。但是那些細節並不是主要的重點。重點是你在寫 CODE 時有時時刻刻記得要盡量減少 BUG 嗎? 你有正確的擬出對策嗎? 來看看上回最後一段範例程式:

 

加上 ASSERT 的算分程式碼[copy code]
        public static int ComputeQuestionScore(XmlElement quiz_question, XmlElement paper_question)        {            int totalScore = 0;            int itemCount = quiz_question.SelectNodes("item").Count;            Trace.Assert(quiz_question != null);            Trace.Assert(paper_question != null);            Trace.Assert(paper_question.SelectNodes("item").Count == quiz_question.SelectNodes("item").Count);            //            //  如果都沒作答, 此題放棄            //            if (paper_question.SelectNodes("item[@checked='true']").Count == 0)            {                return 0;            }            //            //  題目的配分            //            int quiz_score = int.Parse(quiz_question.GetAttribute("score"));            //            //  答對一個選項的分數            //            int item_score = quiz_score / itemCount;            for (int itemPos = 0; itemPos < itemCount; itemPos++)            {                XmlElement quiz_item = quiz_question.SelectNodes("item")[itemPos] as XmlElement;                XmlElement paper_item = paper_question.SelectNodes("item")[itemPos] as XmlElement;                //                //  算成積                //                if (quiz_item.GetAttribute("correct") == paper_item.GetAttribute("checked"))                {                    totalScore += item_score;                }                else                {                    totalScore -= item_score;                }            }            Trace.Assert(totalScore >= (0 - quiz_score));            Trace.Assert(totalScore <= quiz_score);                        return totalScore;        }
   1:  public static int ComputeQuestionScore(XmlElement quiz_question, XmlElement paper_question)
   2:  {
   3:      int totalScore = 0;
   4:      int itemCount = quiz_question.SelectNodes("item").Count;
   5:      Trace.Assert(quiz_question != null);
   6:      Trace.Assert(paper_question != null);
   7:      Trace.Assert(paper_question.SelectNodes("item").Count == quiz_question.SelectNodes("item").Count);
   8:      //
   9:      //  如果都沒作答, 此題放棄
  10:      //
  11:      if (paper_question.SelectNodes("item[@checked='true']").Count == 0)
  12:      {
  13:          return 0;
  14:      }
  15:      //
  16:      //  題目的配分
  17:      //
  18:      int quiz_score = int.Parse(quiz_question.GetAttribute("score"));
  19:      //
  20:      //  答對一個選項的分數
  21:      //
  22:      int item_score = quiz_score / itemCount;
  23:      for (int itemPos = 0; itemPos < itemCount; itemPos++)
  24:      {
  25:          XmlElement quiz_item = quiz_question.SelectNodes("item")[itemPos] as XmlElement;
  26:          XmlElement paper_item = paper_question.SelectNodes("item")[itemPos] as XmlElement;
  27:          //
  28:          //  算成積
  29:          //
  30:          if (quiz_item.GetAttribute("correct") == paper_item.GetAttribute("checked"))
  31:          {
  32:              totalScore += item_score;
  33:          }
  34:          else
  35:          {
  36:              totalScore -= item_score;
  37:          }
  38:      }
  39:      Trace.Assert(totalScore >= (0 - quiz_score));
  40:      Trace.Assert(totalScore <= quiz_score);
  41:      return totalScore;
  42:  }

 

各位仔細看一下加上 ASSERT 的地方。大家寫程式,通常都是腦袋裡想著 "我要處理什麼問題" ,很少人會去想錯誤處理的部份。沒錯,這部份的確是吃力不討好,以此例來說,光是傳進來的參數就有可能狀況百出了。正常的流程都寫不完了,誰還有力氣去把這些錯誤都擋下來?

不過最容易出錯的地方也在這裡。我常在跟其它工程師說,正確的資料 (參數) 傳進來,本來就應該有正確的答案傳出去。難的是錯誤的資料傳進來,你還得回應 "正確" 的錯誤訊息回去,這才真的是個挑戰。這時 ASSERT 的效果就出來了。你可以把 ASSERT 想像成 "宣告" 的子句。以 line 5 ~ 7 行為例:

確保傳入參數是正確的[copy code]
            Trace.Assert(quiz_question != null);            Trace.Assert(paper_question != null);            Trace.Assert(paper_question.SelectNodes("item").Count == quiz_question.SelectNodes("item").Count);
   1:  Trace.Assert(quiz_question != null);
   2:  Trace.Assert(paper_question != null);
   3:  Trace.Assert(paper_question.SelectNodes("item").Count == quiz_question.SelectNodes("item").Count);

 

這三行看在我眼裡,意思就是:

"這兩個參數不能是 NULL,而且兩個 XML ELEMENT 都要有一樣數量的子節點 (Element),否則就不惜代價警告我"

同樣的,在程式的中間,還有傳回值之前,也都可以用同樣的方式來替你的程式 "把關"。再來看看算完成績後,要把值傳回去之前的 CODE:

確保傳回值的範圍正確的程式碼[copy code]
            Trace.Assert(totalScore >= (0 - quiz_score));            Trace.Assert(totalScore <= quiz_score);                        return totalScore;
   1:  Trace.Assert(totalScore >= (0 - quiz_score));
   2:  Trace.Assert(totalScore <= quiz_score);
   3:  return totalScore;

 

這兩行的意思就是:

"不管成績怎麼算,每張答案卷最後的總分一定介於 0 ~ 滿分之間。一樣,有例外的話就不惜代價警告我"

 

聽起來蠻狠的,不惜代價...,不過使用 ASSERT 的話就真的是這樣。通常碰到 ASSERT 後,程式不是進 DEBUGGER 就是直接關掉了。不過請大家注意一下,並不是到處加上 ASSERT 你的程式就沒問題了。要搞清楚加上它的目的是什麼。它要抓的是你程式的 BUG,不是執行期的錯誤 (比如 USER 輸入錯誤的值,或是必填的資料沒填等等)。執行期的錯誤,你還是得乖乖的寫程式,不能用 ASSERT 替代。

舉例來說,如果最後算出來的分數是負的,則會觸動 return 前的 ASSERT。有些有點經驗又有點兩光的 PROGRAMMER 可能會自己顯示一些錯誤訊息。但是這跟本不干 USER 的事啊! 會出現這種情況,錯的一定是 "程式" 本身,也就是你看到 ASSERT 警告後就該來改程式抓 BUG 了。加上 ASSERT 的目的就是在你的程式到處布下眼線,任何一個地方偵測到不對勁,馬上通知你來處理。

當你有心把程式寫好時,你才會覺的這樣作是必要的,而不是累贅。你眼線布的越多,BUG就越難藏在你的程式裡。相對的,如果傳進來的參數就不對了,那應該怎麼辦?

這時就要小心分清楚你要抓的是 BUG 還是做錯誤處理了。如果參數是 USER 直接輸入的,那收到 NULL 或是錯誤的值本來就有可能 (吃芝蔴那有不掉燒餅的...),你需要的是老老實實寫好錯誤處理的流程。但是如果你的 API 早已嚴格定義不接受 NULL,卻還是有白目的工程師硬把 NULL 傳給你的 API,那這時就是 BUG 了,應該用 ASSERT 抓出來,然後找到冤大頭叫他改程式。

不過這樣的 CODE 可不能交到 USER 手上。想像一下如果你正在用 WORD 打文件,結果碰到一個小 BUG,ASSERT 就跳出警告訊息要中止程式,你連存檔都來不及,大概會抓狂吧。這時就是一份程式碼兩種版本的作法發威的地方了。交給 USER 的程式,就應該是切到 RELEASE MODE (或是關掉 ASSERT / TRACE) 編譯的版本。這時所有的 TRACE / ASSERT 好像完全消失一樣,程式就如同一般情況運作。

當 USER 回報一些很難抓到的 BUG 時,這時就可以打開 ASSERT 或是改用 DEBUG BUILD 的版本,再讓 USER 去重現 BUG,這時如果你都有老老實實加上 ASSERT 的話,BINGO,問題在那就一目了然。看看是那一道 ASSERT 指令被觸發,就知道是什麼問題了。抓 BUG 最麻煩的就是找出錯在那裡,而善用 ASSERT 就可以讓 BUG 自己跳出來告訴你出了什麼問題,只要你養成好習慣。

 

再舉一個應用例。看到 Steve Maguire 先生舉這個例子,真是想拍手叫好。他舉了他們在開發 EXCEL 時的例子。EXCEL就是要替試算表作一堆運算,當年還在 DOS 時代,CPU怎樣都不夠快,RAM怎樣都不夠多,程式設計師無不絞盡腦汁,要榨出所有的運算能力,最佳化做到無所不用其極的地步。不過這種東西是錯不得的啊,少算了一塊錢還得了? 碰到這種問題你該怎麼辦?

通常,我們都會先有個安全的版本,算的不快,但是因為邏輯簡單,比較不容易出錯。這種版本寫出來後才開始想盡辦法,去改善程式讓速度加快。馬先生 (ㄜ... 是馬奎爾先生... ) 就充份應用了 ASSERT,隨時都要把 BUG 逼出來的精神,真的把 "驗算" 的方法應用上來。它的作法很簡單,同一張試算表,用兩份不同的程式碼各計算一次,最後再來比對一下結果 (驗算)。只要兩者得到的答案不一樣,那就是出問題了! 當然也有可能是安全的版本寫錯了,不過你至少多了個機會抓到問題,因為不一樣的話,一定 "至少" 有一邊是錯的!

 

沒有這樣的前題的話,各位看到可能都會在心裡想:

"有沒有搞錯,程式都寫不完了,還要寫兩種演算法來驗算?? 老闆又不會多給我一點薪水..."

沒錯,這的確是成本較高的方法,每套系統應該都有關鑑的地方,只要有絕對不能失誤的地方,就值得用這種作法。速度的問題怎麼辦? 很簡單。你只要在 DEBUG MODE 才啟用這 "驗算" 的機制,測試人員輸入各種數值做黑箱測試,如果每次測試的過程中發現驗算錯誤,則 "黑箱" 測試就能幫助你抓到只有 "白箱" 測試才有可能抓到的 BUG !

 

我寫的這個範例程式 (算成績) 其實也準備了兩個版本。上一篇貼的是基本的作法,結果比較可靠。而為了效率我也寫了另一份程式碼,用的是位元運算,希望藉著位元運算,一次就把多選題的答案給算出來。開發的過程中就用了 ASSERT + 驗算的技巧,它不會加快我寫程式的速度,但是它可以加速我找到 BUG 跟解決 BUG 的時間!

 

有沒有覺的這跟單元測試其實很像? 沒錯。單元測試就是一樣的觀念演變出來的作法,所以你用的單元測試 FRAMEWORK 也延用一樣的 ASSERT 使用慣例。你會發現其實之間的觀念都是相通的,只不過單元測試更進一步的把它系統化了,由原本四處藏在程式碼中的 ASSERT,抽出來成為一個一個獨立的 TEST CASE,由原本被動的執行時期檢查,演變為主動執行所有測試的 UNIT TEST。我覺的 Kent 在 XP (extreme programming) 裡舉了一個例子來說明單元測試,比喻的很貼切,我覺的也一樣能拿來比喻 ASSERT:

 

"車子裝了煞車,是要讓車子能開的更快!"

 

聽起來好像很蠢? 煞車明明是讓車子停下來的... 其實不然。想像一下如果你的車子沒煞車,你敢開多快? 了不起就是撞到不會怎麼樣的速度,或是油門放開就停下來的程度而以。有了煞車讓你有信心,碰到危險時你隨時能把車子停下來,你才敢把車子開上高速公路...

 

很有道理的比喻,ASSERT 跟 UNIT TEST 大部份人都覺的是 "煞車",是拖慢你速度用的,但是也因為有這些 "煞車",你才能放心的衝更快。當你有充份運用 ASSERT 的話,你就能很放心的寫程式,沒有後顧之憂。其實類似的關念,Steve Maguire 的書還有提到很多,只不過它的範例都是用 C 寫的 (還不是 C++ ...),看起來會吃力一點。範例程式可能對現今大部份的人都用不到,但是裡面的觀念跟作法還是很有參考價值的,手上還有這本書的人不妨拿起來翻一翻。

 

講到這裡,花了兩篇才講完第一個部份,主要的重點就是用 TRACE / ASSERT 來說明,要讓你的程式夠穩定,第一個要改進的就是你寫程式的想法,觀念及態度。各位不妨以這兩篇的例子,自己回想看看,你做到那幾項:

  1. 你寫程式有考慮到這些問題嗎?
  2. 如果你寫程式有用這些方法,有多少你曾解過的棘手 BUG 會變的迎刃而解的?
  3. 加上 ASSERT 之後,你是否對你程式更有信心了?
  4. 你是否更認同單元測試的必要了?

 

想法跟觀念有了改變,才有可能開發出優良的軟體。你開始認同這樣的想法了嗎? 恭喜你,你已經跨出第一步了。不過光是 BUG FREE 還不足以成為優秀的軟體工程師,這只是必要條件之一而以。除了把程式寫的 "可靠" 之外,接下來的挑戰是如何把程式寫的 "漂亮" ? 下回要開始來探討如何構思你程式碼的結構。什麼樣的結構,什麼樣的方式去分析你的問題,才寫的出架構漂亮的程式? 別急,請期待續篇 :D

 

 

--

註: 範例程式很多 CODE 被我跳過去了,有興趣的人可以抓回去研究看看... 請點 [這裡] 下載。






精選文章

RUN! PC 文章及範例下載
2008/11. 生產線模式的多執行緒應用
2008/09. 用ThreadPool發揮CPU運算能力
2008/06. SEMAPHORE在ASP.NET的應用
2008/04. 以ASP.NET開發同步WEB應用程式

如何學好 "寫程式" 系列
#1. 該如何學好 "寫程式" ??
#2. 為什麼 programmer 該學資料結構 ??
#3. 進階應用 - 資料結構 + 問題分析
#4. 你的程式夠 "可靠" 嗎?

#5. 善用 TRACE / ASSERT

安德魯是誰?

Andrew Wu | Create Your Badge

我喜歡鑽研物件導向、軟體工程及作業系統等相關技術。我會在這裡發表我的研究心得,也當作我自己的學習筆記。


Recent comments

Comment RSS