1. 敗家: HTPC

     

    image

    沒錯,我又敗家了... :D

     

    想弄 HTPC 已經很久了,不過老是碰到一些怪問題。原本想直接用桌機,不過桌機已經接了雙螢幕,要再接 LCD TV,沒在主機板上插兩張 VGA 卡就辦不到 (其它用 PCI / USB 之類的效能太差就不考慮了)。不過三個輸出都需要數位輸出 (兩台桌上的 LCD 都有 DVI,SHARP TV 又很有個性的不提供 D-SUB,只有 HDMI ... 不然就是色差...),沒有插兩張 DUAL DVI 的中價位 VGA 卡也沒辦法...。我用不到這麼高級的顯示卡啊 :~~~

    不過就算硬體搞定了也是個大問題。試過暫時接到 TV 過,有時我要 MCE 開出來開在桌上的 LCD,有時又要開在 TV ... 光是操作就很麻煩...。最後體認到了沒有弄一台專用機的話,HTPC大概只能試一下試爽的而以。

    與其是想升級換張可以插兩張顯卡的主機板 + 新的顯卡,實在不是太便宜... 尤其我須要的是 DVI x 2 + HDMI ... 後來無意間看到一堆在介紹 AMD 780G 的文章... 算一算好像比換主機板還便宜...。

    於是不知不覺,八月初某日下班就多提著一袋零件回來了... 哈哈。因為有一堆東西有備料,陸陸續續買了下列東西就開始享受了:

    1. 主機板: ASUS M3A78-EM NTD 2750
    2. CPU: AMD 4850e (45W 低耗電) NTD 2250
    3. RAM: DDR2-800 2GB x 2 (實在用不到這麼多,不過看到一條 999,真的找不到理由去買 1G 的...) NTD 990 x 2
    4. POWER: SEASONIC 330W (這個也很奢侈,明明整台機器全速運轉也沒有破 100W ... 不過其它牌都不信賴... 330W 是最低的規格了) NTD 1700
    5. 搖控器: Toshiba MCE 搖控器 NTD 550
    6. 便宜的 HDMI 線 3 公尺 NTD 130
    7. 喇叭: JungleRex AF-31S NTD 3800
    8. 現有的硬碟: Hitachi 7K250 250GB SATA2 HDD
    9. 現有的舊機殼、光碟機、藍芽接收器、鍵盤滑鼠... ETC

    其它像機殼,光碟機,硬碟機,鍵盤滑鼠等等就直接用現成的...。至於 MCE 一定要的 TV CARD 我反而沒買...。一方面現在的卡我都覺的太貴了 (因為之前買過 ATI theater pro 550 的卡,一張不到一千...),再加上我很少在客廳看 LIVE TV...,就算要看也是先用 MCE 錄下來。我跟家裡大人的桌機都已經有 MCE + TV 卡了,實在不大需要再買第三張... 哈哈。

    果然裝了 HTPC 這樣才覺的這台電視真的是好物啊 :D,隨便一部 DVD 或是 MPEG4 的片子 (來源當然是牧場來的...) 看起來都很棒。扣掉奢侈品,其實一萬以內都搞定了。硬體沒啥好介紹的,網路一查一堆,總是有魔人有一堆測試報告,看都看不完,輪不到我在這邊講。除了耗電量實在很低一定要講一下之外... 其它硬體介紹我就跳過去了。耗電量實在是不錯,一開機耗電量 85 W 左右,開機完進入 VISTA 後就降到 50 ~ 55W,看片時看 DVD (MPEG2) 或是 AVI ( DIVX / MPEG4 ),耗電量輕微升到 60 ~ 65W,跑一些變態的 BENCHMARK 才會飆到 75 ~ 80W...。進入待命模式才 1 ~ 2W,真的是算省電了。

     

    那特別要講的是啥? 主要要講怎樣不靠鍵盤滑鼠操作 MCE ... :D

    不用 KB / MOUSE,那當然是用搖控器了。我買的這個我覺的很不錯,接收器插上去就可以用,什麼 DRIVER 設定都免了,VISTA X64 也一切正常。搖控器上那顆綠綠的鈕,按下去就會自動叫出 MCE。叫出 MCE 後,主要的幾個功能也都有對應的按鈕,操作起來很方便。只要可以直接在 MCE 裡播放的話,用搖控器就搞定了。現在我連放 MP3 (原本用 Media Player) 跟放照片 (Windows Live Photo Gallery) 也都改用 MCE 了... (Y) 上面那張圖就是用 MCE 放 MP3 的樣子...。

    搖控器上的電源鈕,也會直接對應到電腦的 "SLEEP" 及喚醒 SLEEP 中的電腦。如果你都只用 MCE,其實操作就很簡單了。按下搖控器的開關,電腦就醒來。看完再按一下電腦就 SLEEP 去了。康博的 TV CARD 使用者比較幸福,卡片上可以直接對應到電腦的電源開關。不過... 開機關機就要等的比較久。算算 SLEEP 模式下耗掉 1W 左右的電,就算了... 隨時想看一按就開還是比較爽啊..

     

    另一個比較特別的也要講一下。過去曾貼過一篇 [一萬多塊錢的簡報器],提到有軟體 (Salling Clicker for Windows 3.5)可以當簡報器,搖控 PPT 播放,也能搖控 Media Player ...,現在這些東西有內建在 VISTA 的版本了,主要就是透過 SlideShow。在 Vista 推出一年多之後,Microsoft 總算推出 Preview 版的軟體,可以讓你的 Windows Mobile 5 / 6 的手機及PDA,搖身一變成為 Vista SlideShow Device。再搭配上 Microsoft 才開發到 BETA 版的 SlideShow Gadget for MCE... 耶! 手機可以變成 MCE 搖控器了!

    image image

    這跟 MCE 搖控器有什麼不一樣? 這篇文章有簡單的介紹... 這裡也找到另一篇...。首先,TV 的節目表都可以直接秀在手機上,播放的曲目也都會顯示在手機上,如果你不希望中斷影片的播放,這就很有用。這些功能是沒有螢幕的搖控器作不到的。不過... 大部份時間,還是搖控器好用。

    但是為什麼我還是要特別提一下? 因為... 我還要聽音樂啊,聽音樂時我就懶的開電視了。不開電視的情況下,這就很好用了,直接在手機上選好媒體櫃裡的專輯,按下播放,整個客廳就有音樂可以聽了 :D,這完全是搖控器辦不到的 :D

     

    Microsoft Vista Slideshow Media Centre Remote

    不過這兩種搖控方式不能合二為一嗎? 有... 只要肯花錢就有。這支搖控器就是符合 vista slideshow device 規格的 MCE 搖控器,一隻要 USD 199... 看到價錢,我還是買隻十分之一不到的搖控器 + 手機就好...

    有打算用 HTPC 又有 WM5/WM6 手機的人一定要試一下。反正通通是現成的啊 (除了藍芽接收器你可能得花個一百塊買一支之外)。如果你也是 HTPC 一族的,有啥 TIPS 記得提供一下,如果不是的話,快點去敗吧 (H)!

    2008/08/25 敗家 有的沒的

  2. 世紀末軟體革命復刻版

    http://www.books.com.tw/exep/prod/booksfile.php?item=0010334718

     

    沒想到電腦書也流行復古... 真是令人懷念的一本書啊,電腦書通常都是半年到一年就過時了,堆了兩年以上的書很少還會拿出來翻... 不過這本 (原版,不是復刻版) 例外,足足有好幾年的時間我還是會沒事就拿起來翻一翻。每想到十來年後還會有書局再版... 一定要推一下。

    這本書的觀念寫的實在不錯,我有很多OOP的觀念都是靠這本書建立起來的。常常有人問我學物件導向要看那本書,我都很想推這一本,不過早就絕版了那買的到... 前陣子才發現竟然有復刻版... :D

    再版的這本書是把原本上下兩冊併在一起,不知道有沒有附當年作者自己寫的 GUI 範例程式? 老實說那個範例程式在當年是蠻猛的,一張磁片就放的下的程式 (還包含 source code),一個像 windows 3.1 的視窗環境,該有的功能一點都不少,你還可以用它的 API 開發在那個 GUI 環境下的應用程式...,不過那個東西實際的用途不大,現在搞不好也沒有環境能跑了,不過看到有人可以用 C++ 建立起像 Windows 3.1 那樣的環境,而且真的可以跑,還真是打從心底佩服...。

    技術可能都退流行了,現在流行的東西書上都沒提到,不過觀念的部份還是很實用,想加強自己物件導向觀念的人不要錯過這本書... [Y]

    2008/08/23 技術隨筆 敗家 物件導向

  3. Tips: 踢掉遠端桌面連線的使用者

    近日同事連不進 SERVER,因為連線人數已滿,又摸不到本機,正在那邊苦惱...。原來大家都知道怎麼連,但是都不知道怎麼砍人... 。從 windows 2000 開始就有 RDP 可以用了,當時學到的一個指令一直到現在都可以用,就藉這個機會貼一下。

    什麼秘技都一樣,說穿了就不值錢。半年前貼了一篇 [遠端桌面連線的小技巧],裡面講到加上 /console 這參數就能連到 console session,不會跟其它人去搶那兩個連線,就可以把不順眼的 USER 砍了。連進去後要砍人很簡單,工作管理員叫出來,最後一頁 [USER] 就會列出有多少人掛在上面...

    image

    通常這樣就能解決 90% 的問題了。如果連這個秘密連線都被用掉了,那只剩另一招: TSDISCON.exe

     

    image

    TSDISCON: Disconnects a terminal session. 讚! 就是要這種東西... 用法很簡單,如果你有遠端 SERVER 的管理者權限,防火牆又沒把 NETBIOS 關掉,那麼可以這樣用:

     

    1. 先登入遠端 server
      NET USE \\MYSERVER /user:MYACCOUNT *
    2. 踢掉其它人
      TSDISCON 1 /SERVER:MYSERVER

    BINGO,其中要注意一下就是 SESSION ID,也就是上面工作管理員 ID 那一欄。0 代表 console,其它就是額外的連線。不過除非你有另外買 LICENCE,否則 OS 內建的授權只有兩個連線,意思就是亂猜一通,1 跟 2 隨便挑一個砍了就好...

    指令成功的話,被你挑中的連線就會中斷了。趁對方還沒重新連上去之前,快點連進去佔名額吧 :D

    2008/08/23 Tips 有的沒的

  4. Thread Sync #2. 實作篇 - 互相等待的兩個執行緒

    上篇有人跟我講太深奧了... Orz, 其實不會,只是還沒看到 Code 而以...。就先來幫黑暗魔人賽說明一下程式碼...。首先來看的是黑暗大魔王: GameHost..

     

    GameHost 呼叫 Player 的片段[copy code]
        public void Start(Player p)    {        // 略...        int[] guess = p.StartGuess(_maxNum, _digits);        // 略...        Hint hint = compare(guess);        // 略...        while (hint.A != _digits)        {            // 略...            guess = p.GuessNext(hint);            // 略...            hint = compare(guess);        }        p.Stop();        // 略...    }
    
       1:  public void Start(Player p)
    
       2:  {
    
       3:      // 略...
    
       4:      int[] guess = p.StartGuess(_maxNum, _digits);
    
       5:      // 略...
    
       6:      Hint hint = compare(guess);
    
       7:      // 略...
    
       8:      while (hint.A != _digits)
    
       9:      {
    
      10:          // 略...
    
      11:          guess = p.GuessNext(hint);
    
      12:          // 略...
    
      13:          hint = compare(guess);
    
      14:      }
    
      15:      p.Stop();
    
      16:      // 略...
    
      17:  }
    

     

    這段程式完全是老闆的角度在思考。抓到 PLAYER 後就叫它開始猜 StartGuess(),然後拼命的叫 PLAYER 再猜 GuessNext(), 直到猜中才可以休息 Stop()

    很典型的多型 ( Polymorphism ) 應用,實際上會 RUN 什麼 CODE,就看繼承 PLAYER 的人是怎麼寫的...。這次我們再從弱勢勞工的角度來看看 PLAYER 該怎麼實作 (以 darkthread 附的 DummyPlayer 為例):

     

    Player 實作的範例 ( DummyPlayer )[copy code]
    public class DummyPlayer : Player{    private int[] _currAnswer = null;    private Random _rnd = new Random();    private void randomGuess()    {        List<int> lst = new List<int>();        for (int i = 0; i < _digits; i++)        {            int r = _rnd.Next(_maxNum);            while (lst.Contains(r))                r = _rnd.Next(_maxNum);            lst.Add(r);            _currAnswer[i] = r;        }    }    public override int[] StartGuess(int maxNum, int digits)    {        base.StartGuess(maxNum, digits);        _currAnswer = new int[digits];        randomGuess();        return _currAnswer;    }    public override int[] GuessNext(Hint lastHint)    {        randomGuess();        return _currAnswer;    }}
    
       1:  public class DummyPlayer : Player
    
       2:  {
    
       3:      private int[] _currAnswer = null;
    
       4:      private Random _rnd = new Random();
    
       5:   
    
       6:      private void randomGuess()
    
       7:      {
    
       8:          List<int> lst = new List<int>();
    
       9:          for (int i = 0; i < _digits; i++)
    
      10:          {
    
      11:              int r = _rnd.Next(_maxNum);
    
      12:              while (lst.Contains(r))
    
      13:                  r = _rnd.Next(_maxNum);
    
      14:              lst.Add(r);
    
      15:              _currAnswer[i] = r;
    
      16:          }
    
      17:      }
    
      18:   
    
      19:      public override int[] StartGuess(int maxNum, int digits)
    
      20:      {
    
      21:          base.StartGuess(maxNum, digits);
    
      22:          _currAnswer = new int[digits];
    
      23:          randomGuess();
    
      24:          return _currAnswer;
    
      25:      }
    
      26:      public override int[] GuessNext(Hint lastHint)
    
      27:      {
    
      28:          randomGuess();
    
      29:          return _currAnswer;
    
      30:      }
    
      31:  }
    

     

    因為 CODE 不多,我就不刪了,全文照貼。另一個原因是我想讓各位看看拆成好幾段的 CODE 是不是還能夠一眼就還原成原來的邏輯?  如果只看這段 CODE 十秒鐘,沒有看註解或說明,誰能馬上回答這段 CODE 解題的邏輯是什麼?

     

    別誤會,不是指這 CODE 不易讀,而是因為呼叫的方式邏輯被迫配合 GameHost 而被切散了,你得再重新把它拼湊起來。它的邏輯很簡單,甚至簡單到連問題的答案都被忽略掉了,不過就每次都隨機丟個數字回去,在 StartGuess( ) 及 GuessNext( ) 都是。

     

     

    可憐的勞動階級要站起來啊~ 先幻想一下,如果勞工 (PLAYER) 才是老闆,那麼程式可以改成怎麼樣? 這也才是我們本篇的主角。先來看看成果再回頭來看怎麼實作。這次看的是修改後的版本: AsyncDummyPlayer.

     

    換 PLAYER 的角度思考的邏輯: AsyncDummyPlayer[copy code]
        public class AsyncDummyPlayer : AsyncPlayer    {        private int[] _currAnswer = null;        private Random _rnd = new Random();        private void randomGuess()        {            List<int> lst = new List<int>();            for (int i = 0; i < _digits; i++)            {                int r = _rnd.Next(_maxNum);                while (lst.Contains(r))                    r = _rnd.Next(_maxNum);                lst.Add(r);                _currAnswer[i] = r;            }        }        protected override void Init(int maxNum, int digits)        {            _currAnswer = new int[digits];        }        protected override void Think()        {            while (true)            {                this.randomGuess();                Hint h = this.GameHost_AskQuestion(this._currAnswer);                if (h.A == this._digits) break;            }        }    }
    
       1:  public class AsyncDummyPlayer : AsyncPlayer
    
       2:  {
    
       3:      private int[] _currAnswer = null;
    
       4:      private Random _rnd = new Random();
    
       5:      private void randomGuess()
    
       6:      {
    
       7:          List<int> lst = new List<int>();
    
       8:          for (int i = 0; i < _digits; i++)
    
       9:          {
    
      10:              int r = _rnd.Next(_maxNum);
    
      11:              while (lst.Contains(r))
    
      12:                  r = _rnd.Next(_maxNum);
    
      13:              lst.Add(r);
    
      14:              _currAnswer[i] = r;
    
      15:          }
    
      16:      }
    
      17:      protected override void Init(int maxNum, int digits)
    
      18:      {
    
      19:          _currAnswer = new int[digits];
    
      20:      }
    
      21:      protected override void Think()
    
      22:      {
    
      23:          while (true)
    
      24:          {
    
      25:              this.randomGuess();
    
      26:              Hint h = this.GameHost_AskQuestion(this._currAnswer);
    
      27:              if (h.A == this._digits) break;
    
      28:          }
    
      29:      }
    
      30:  }
    

     

    程式碼也沒比較少,都差不多。不過是那堆 CODE 換個地方擺而以。但是仔細看看,這個版本的邏輯清楚多了,PLAYER 一開始就是執行 Init( ) 的部份,而 GameHost 叫 Player 開始解題時, Player 就開始思考 (Think),而這個無腦的 Player 也很直接,就一直執行 while (true) { .... } 這個無窮迴圈,直到亂猜猜中為止。

    如果 Player 在思考的時,不管在那裡它都可以適時的呼叫 GameHost_AskQuestion( .... ) 來跟 GameHost 問答案。什麼時後該猜數字? 該猜什麼數字? 這正是整個 Player 的核心,也就是 "怎麼猜" 這件事。以人的思考方式一定會分階段,比如一開始先把所有數字猜一輪,有個概念後再想想怎麼猜能更逼近答案,最後才是致命的一擊,找出正確答案送出去,贏得比賽。

    這樣的作法,如果套在 DummyPlayer (原版本),每個階段都要塞在一個大的 switch case, 放在 GuessNext( ) 裡。而現在是那個階段? 只能靠 instance variable 了,先存著等到下次又被呼叫時再拿出來回想一下,上回作到那...。

    而第二個版本,則完全沒這個問題,就把它當作一般程式思考就夠了,第一階段就是一個 LOOP,有它自己用的一些變數。第一階段處理完畢就離開 LOOP 繼續執行後面的 CODE... 直到最後離開 Think( ) 這個 method (認輸) 或是猜中答案光榮返鄉...。

     

     

    兩者的差別看出來了嗎? DummyPlayer 像是被動的勞工,老闆說一動他就作一動。第一動作完就拿個筆計記下來,等著下次老闆再叫他,他就翻翻筆記看看之前做到那,這次繼續...。

    而 AsyncDummyPlayer 這個主動的勞工呢? 老闆交待給他一件任務後,他就自己思考起該怎麼做了。中間都不需要老闆下令。反而是過程中勞工需要老闆的協助時,老闆再適時伸出援手就可以了,一切雜務都由這位主動優秀的勞工自己處理掉。

     

     

     

    有沒有差這麼多? 這麼神奇? 是怎麼辦到的? 先來看看類別關系圖:

    ThreadSync

     

    上圖中,AsyncPlayer 就是改變這種型態的關鍵類別。AsyncPlayer 會用我們在上一篇講到的關念,化被動為主動,轉換這兩種呼叫模式。先來看看這個類別的程式碼到底變了什麼把戲,可以讓弱勢的勞工也有自主的權力?

     

     

     

     

    AsyncPlayer 實作: 化被動為主動[copy code]
        public abstract class AsyncPlayer : Player    {        public override int[] StartGuess(int maxNum, int digits)        {            base.StartGuess(maxNum, digits);            Thread thinkThread = new Thread(this.ThinkCaller);            thinkThread.Start();                        this._host_return.WaitOne();            return this._temp_number;        }        public override int[] GuessNext(Hint lastHint)        {            this._temp_hint = lastHint;            this._host_call.Set();            this._host_return.WaitOne();            return this._temp_number;        }        public override void Stop()        {            base.Stop();            this._temp_hint = new Hint(this._digits, 0);            this._host_call.Set();            this._host_end.WaitOne();            this._host_complete = true;        }        private void ThinkCaller()        {            try            {                this.Init(this._maxNum, this._digits);                this.Think();            }            catch (Exception ex)            {                Console.WriteLine("Player Exception: {0}", ex);            }            finally            {                this._host_end.Set();            }        }        protected abstract void Init(int maxNum, int digits);        protected abstract void Think();        private AutoResetEvent _host_call = new AutoResetEvent(false);        private AutoResetEvent _host_return = new AutoResetEvent(false);        private AutoResetEvent _host_end = new AutoResetEvent(false);        private bool _host_complete = false;        private int[] _temp_number;        private Hint _temp_hint;        protected Hint GameHost_AskQuestion(int[] number)        {            if (this._host_complete == true) throw new InvalidOperationException("GameHost stopped!");            lock (this)            {                try                {                    this._temp_number = number;                    this._host_return.Set();                    this._host_call.WaitOne();                    return this._temp_hint;                }                finally {                    this._temp_number = null;                    this._temp_hint = new Hint(-1, -1);                }            }        }    }
    
       1:  public abstract class AsyncPlayer : Player
    
       2:  {
    
       3:      public override int[] StartGuess(int maxNum, int digits)
    
       4:      {
    
       5:          base.StartGuess(maxNum, digits);
    
       6:          Thread thinkThread = new Thread(this.ThinkCaller);
    
       7:          thinkThread.Start();
    
       8:          this._host_return.WaitOne();
    
       9:          return this._temp_number;
    
      10:      }
    
      11:      public override int[] GuessNext(Hint lastHint)
    
      12:      {
    
      13:          this._temp_hint = lastHint;
    
      14:          this._host_call.Set();
    
      15:          this._host_return.WaitOne();
    
      16:          return this._temp_number;
    
      17:      }
    
      18:      public override void Stop()
    
      19:      {
    
      20:          base.Stop();
    
      21:          this._temp_hint = new Hint(this._digits, 0);
    
      22:          this._host_call.Set();
    
      23:          this._host_end.WaitOne();
    
      24:          this._host_complete = true;
    
      25:      }
    
      26:      private void ThinkCaller()
    
      27:      {
    
      28:          try
    
      29:          {
    
      30:              this.Init(this._maxNum, this._digits);
    
      31:              this.Think();
    
      32:          }
    
      33:          catch (Exception ex)
    
      34:          {
    
      35:              Console.WriteLine("Player Exception: {0}", ex);
    
      36:          }
    
      37:          finally
    
      38:          {
    
      39:              this._host_end.Set();
    
      40:          }
    
      41:      }
    
      42:      protected abstract void Init(int maxNum, int digits);
    
      43:      protected abstract void Think();
    
      44:      private AutoResetEvent _host_call = new AutoResetEvent(false);
    
      45:      private AutoResetEvent _host_return = new AutoResetEvent(false);
    
      46:      private AutoResetEvent _host_end = new AutoResetEvent(false);
    
      47:      private bool _host_complete = false;
    
      48:      private int[] _temp_number;
    
      49:      private Hint _temp_hint;
    
      50:      protected Hint GameHost_AskQuestion(int[] number)
    
      51:      {
    
      52:          if (this._host_complete == true) throw new InvalidOperationException("GameHost stopped!");
    
      53:          lock (this)
    
      54:          {
    
      55:              try
    
      56:              {
    
      57:                  this._temp_number = number;
    
      58:                  this._host_return.Set();
    
      59:                  this._host_call.WaitOne();
    
      60:                  return this._temp_hint;
    
      61:              }
    
      62:              finally {
    
      63:                  this._temp_number = null;
    
      64:                  this._temp_hint = new Hint(-1, -1);
    
      65:              }
    
      66:          }
    
      67:      }
    
      68:  }
    

     

    這段程式碼長了一點,內容也都刪不得,各位請耐心點看。上一篇我畫了張概念性的時序圖,這次我們再拿同一張圖,不過這次會標上程式碼:

     ThreadSync2

     

     

    請注意一下各個箭頭的上下順序。由上往下代表時間的進行,如果應該在後面執行的 CODE 不巧先被呼叫了,則動作較快的那個 THREAD 會被迫暫停,等待另一邊的進度跟上。先來看看 StartGuess( ) 怎麼跟 Think( ) 互動:

    StartGuess(...)[copy code]
            public override int[] StartGuess(int maxNum, int digits)        {            base.StartGuess(maxNum, digits);            Thread thinkThread = new Thread(this.ThinkCaller);            thinkThread.Start();                        this._host_return.WaitOne();            return this._temp_number;        }
    
       1:  public override int[] StartGuess(int maxNum, int digits)
    
       2:  {
    
       3:      base.StartGuess(maxNum, digits);
    
       4:      Thread thinkThread = new Thread(this.ThinkCaller);
    
       5:      thinkThread.Start();
    
       6:      this._host_return.WaitOne();
    
       7:      return this._temp_number;
    
       8:  }
    

     

    GameHost 呼叫 Player.StartGuess( ) 有兩個目的,一個是給 Player 題目範圍,讓 Player 做好準備動作。另一個則是準備好之後 GameHost 要取得 Player 傳回的第一個問題。

    程式碼很忠實的做了一樣的事,只不過 StartGuess( ) 建立了新的執行緒來負責。新的執行緒會執行 ThinkCaller( ),啟動之後 GameHost 這邊就什麼都不作,等待 _host_return 這個 WaitHandle 被叫醒,代表另一邊已經做好了,可以從共用變數 _temp_number 取得問題傳回去。

    既然 GameHost 在等待某人通知它,我們就來看看是誰會通知他題目已經準備好了:

     

    GameHost_AskQuestion(...)[copy code]
            protected Hint GameHost_AskQuestion(int[] number)        {            if (this._host_complete == true) throw new InvalidOperationException("GameHost stopped!");            lock (this)            {                try                {                    this._temp_number = number;                    this._host_return.Set();                    this._host_call.WaitOne();                    return this._temp_hint;                }                finally {                    this._temp_number = null;                    this._temp_hint = new Hint(-1, -1);                }            }        }
    
       1:  protected Hint GameHost_AskQuestion(int[] number)
    
       2:  {
    
       3:      if (this._host_complete == true) throw new InvalidOperationException("GameHost stopped!");
    
       4:      lock (this)
    
       5:      {
    
       6:          try
    
       7:          {
    
       8:              this._temp_number = number;
    
       9:              this._host_return.Set();
    
      10:              this._host_call.WaitOne();
    
      11:              return this._temp_hint;
    
      12:          }
    
      13:          finally {
    
      14:              this._temp_number = null;
    
      15:              this._temp_hint = new Hint(-1, -1);
    
      16:          }
    
      17:      }
    
      18:  }
    

     

    就在 GameHost 正在等題目的時後,另一個執行緒正在進行 "思考" 的動作,直到有結論後會呼叫 GameHost_AskQuestion( ... ) 送出問題。這時這個問題會被放到 _temp_number, 而下一步就是 _host_return.Set( ), 通知另一個執行緒,正在等這個結果的人: "喂! 東西已經準備好了,可以來取貨了!!"

    整個機制就這樣串起來了。GameHost 那邊怎麼把答案傳回來? 同樣的作法,反過來而以。GameHost 會藉著呼叫 Player.GuessNext(...) 把答案傳回來,而這時就觸動一樣的機制,讓另一邊 Player Thread 呼叫的 GameHost_AskQuestion( ... ) 醒過來,把答案拿走, RETURN 回去。

    這樣一直重複下去,剩下最後一個同步的點,就是結束遊戲的地方。說穿了也是一樣的把戲,只是這次是藉著 GameHost 呼叫 Player.Stop( ),而另一邊 Player Thread 執行完 Think( ) 後,兩邊就一起結束遊戲了。

     

    總算講完了。其實 thread 能解決的問題還真是五花八門。每次當我想出這些方法來簡化問題時,我就會覺的很有成就感。雖然寫出這個不會讓我贏得比賽,反而因為同步的關係,AsyncDummyPlayer 執行的速度還遠遠落後 DummyPlayer (我的機器跑起來,大概差了四~五倍 ...) 。不過我知道我簡單的頭腦,不先把問題簡化的話,我大概解決不了太複雜的問題...。也許是缺了這種能力,才讓我更有動力去想簡化問題的方式吧?

     

    最後,為什麼每次講到 thread 的文章,都是 code 一點點,文章跟圖一大堆? 咳咳... 難道我也到了靠一張嘴混日子的地步了嘛? Orz... 本系列到此結束,以後還會有什麼主題? 想到再說啦~~ 今天各位記得去拜拜~~ 下回見!

    2008/08/15 系列文章: 多執行緒的處理技巧 .NET Tips 作業系統 多執行緒 技術隨筆 物件導向

  5. 話題人物?

    之前因為被盜文,除了被弄的不大爽之外,倒也沒什麼事,不過有人給我個 link,無意間發現對岸竟然有這麼篇文章...

    (最原始的網頁不知為何被下架了,底下提供的 LINK 是目前還連的到的...)

    URL: http://www.cnblogs.com/hullfqaz/archive/2008/07/28/1254315.html

     

    image

     

    看了又好氣又好笑... 沒想到對岸有網友在討論我碰到的事件啊,底下還真的有人回應批評百度...。前同事 在 MSN 跟我打屁,說這篇引用我的事例來討論的網站,也一樣沒有經過我的同意就轉貼,我的言論可能會引起兩岸對立,引發第三次世界大戰... Orz...

    這次好一點,雖然也是原文照貼我的文章,至少是全文照貼,頭尾都留著,LINK 也留著,沒有很 "自動" 的翻成簡體中文...後來好奇用相關關鑑字再去 GOOGLE 找看看,多找到一篇:

    URL: http://blog.const.net.cn/news/20080728/ec3b4ba924bde2b4.htm

     

    不過這次運氣差了點,我的 IP 被封鎖不能看... 我應該沒有偉大到讓對方直接封我的 IP 吧?

    image

     

    不過人氣好像不大夠,搜了半天才搜到這兩篇... 看來要上頭條新聞還久的很.. 哈哈..

    2008/08/14 有的沒的 火大