Posts tagged with @Programming

An Example of BackgroundWorker

其實這篇文章跟上一篇是有點關聯的…… 我為了解決 Memory Leak ,抱著「反正我本來就還有另一個問題要靠 BackgroundWorker 來解決,不如碰碰運氣」的心態,將耗時又耗記憶體的那段程式改寫為 BackgroundWorker ,結果真的有用耶~ 所以我沒有嘗試 GC.Collect() ,因為用這種有副作用的 function 會覺得不太漂亮 :p

BackgroundWorker 的效果跟 Thread 差不多,但更容易使用,方法如下:

  1. 把 BackgroundWorker 元件拉進來,取名為 BGWorker1
  2. 將 WorkerReportsProgress 設為 True ,讓 BackgroundWorker 回報進度。
  3. 雙擊 DoWork 事件,然後如下撰寫:

    private void BGWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        // 另外根據 sender 建立一個 BackgroundWorker
        BackgroundWorker bw = sender as BackgroundWorker;
    
        // 取出參數,我不知道怎麼改成能夠取多參數的版本… 此例中 YourFunction 的第二個參數是 int
        int arg = (int)e.Argument;
    
        // 開始執行你要的工作
        e.Result = YourFunction(bw, arg);
    
        // 處理使用者按下「取消」的動作
        if (bw.CancellationPending)
        {
            e.Cancel = true;
        }
    }
    
  4. 雙擊 RunWorkerCompleted 事件,然後如下撰寫:

    private void BGWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Cancelled)
        {
            // 使用者中止了執行
        }
        else if (e.Error != null)
        {
            // 執行中發生錯誤,可用 e.Error.Message 得到錯誤訊息
        }
        else
        {
            // 執行作業正常完成
        }
    }
    
  5. 雙擊 ProgressChanged 事件,然後如下撰寫:

    private void BGWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        // 用 e.ProgressPercentage 可以取得目前執行進度百分比(0-100)
    }
    

    比較實用的範例:

    private long begin_tick = 0L;
    
    private void BGWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        // 取得工作開始的時間,記得要在 RunWorkerCompleted 中將 begin_tick 設回 0L
        // C# 中有 function scope 的 static variable 嗎?
        if (begin_tick.Equals(0L))
        {
            begin_tick = DateTime.Now.Ticks;
        }
    
        // 計算已執行時間
        TimeSpan elapse_time = new TimeSpan(DateTime.Now.Ticks - begin_tick);
    
        // 預估完整執行所需時間
        long total_tick = elapse_time.Ticks * 100L;
        // 避免 Division by Zero…… 這有更方便的寫法嗎?
        if (!e.ProgressPercentage.Equals(0))
        {
            total_tick /= e.ProgressPercentage;
        }
    
        // 預估剩餘時間
        TimeSpan remain_time = new TimeSpan(total_tick - elapse_time.Ticks);
    
        // 預估完成時間
        DateTime finish_time = new DateTime(DateTime.Now.Ticks + remain_time.Ticks, DateTimeKind.Local);
    
        ProgressBar.Value = e.ProgressPercentage;
        StatusLabel.Text = String.Format("{0}% (經過時間:{1:00}:{2:00}:{3:00} / 預估剩餘:{4:00}:{5:00}:{6:00} / 預估完成:{7})", e.ProgressPercentage, elapse_time.Hours, elapse_time.Minutes, elapse_time.Seconds, remain_time.Hours, remain_time.Minutes, remain_time.Seconds, finish_time.ToString("yyyy-MM-dd HH:mm:ss"));
    }
    
  6. 至於 YourFunction 則類似這樣:

    private void YourFunction(BackgroundWorker bw, int progress)
    {
        if (!BGWorker1.CancellationPending)
        {
            // 執行你要執行的作業
    
            // 回報目前執行進度百分比(0-100),沒錯,你必須自己計算
            BGWorker1.ReportProgress(progress);
        }
    }
    

    傳入參數是陣列,使用 for 迴圈的範例:

    private void YourFunction(BackgroundWorker bw, string[] argument)
    {
        int count = argument.Length;
        for (int i = 0; i < count; i++) {
            if (!BGWorker1.CancellationPending)
            {
                using (SomeClass obj = new SomeClass(argument[i])) {
                    BGWorker1.ReportProgress(i * 100 / count);
                }
            }
        }
    }
    

20080624 ~woof!

Woof! (release) (by BCSEEATI)

由於快要退宿了,最近我總是開冷氣睡覺,在冷氣房相當容易入睡,就算我本來很有精神也一樣 XD 我經常在冷氣自動關機後熱醒,但是又懶得起來而大賴床,今天就是在這種情況下,睡了十個小時才醒來。

起床後我便前往 Lab 寫 Delphi 。我這次盡量很有架構地來寫,將程式分成了七個子系統1,但最後有兩個子系統2只做出了雛型、兩個子系統3還缺很多東西,只有三個子系統4是完成的。

Demo 將至,所以許多同學也在 Lab 寫 Delphi ,我斷斷續續地寫到下午兩點,才回宿舍洗澡,本來沒有預定要睡午覺,卻因為冷氣太舒服,我就睡了…… XD 我設了下午六點的鬧鐘,但這並沒有叫醒我,到了晚上七點半,熊打電話來,他說:「你在哪裡?七點半了耶!快來!」,類似的電話我平常也常接到,大多是找我去 Lab 開戰,所以我以為不是什麼大事。下床後連上遠端,看見 MSN 的視窗中熊說「7:30 上課」,我完全醒了,打電話確認後,熊說老師明天有事,所以提前上課…… 囧rz 我對這件事完全沒有印象啊~

這時候我的程式還是那幾個子系統分割得很好,還沒有開始整合,說得淺白一點就是完全不能 demo ,實在相當糟糕。我只能直接到教室去連遠端趕工,然後把握時間,在同學們報告完以前,整合好那三個已完成的子系統。還好我一開始就考慮過未來的整合,所以完成的這些子系統還滿容易整合的,大約半小時就把大部分都整合完畢,然後再花一小時把我忘記的部份5也都整合好、 de 了一點點之前沒有想到的 bug6 ,然後還滿有餘裕地上台 demo ,並沒有拖到壓軸 XD

這次我的 MSN 又是在靠小花招取勝,例如花俏的介面、富文本訊息7等,不過看起來有成功地唬到老師,老師相當驚訝我的訊息框怎麼能插入圖片,還問我是用什麼語言寫的(兩年前就沒這麼驚訝啊~)。我回答說是用 Delphi ,然後那個訊息框其實是 TWebBrowser 元件,老師還懷疑了一下 Delphi 有這個元件嗎?(老師明明自己就教過我們啊~)嗯、我想老師應該是對於要怎麼直接編輯 TWebBrowser 中的 HTML 有疑問吧!並不是一時忘記 Delphi 有這個元件 XD

晚上由於大家都累了或是有事,所以不能在 Lab 開戰,滿遺憾的 XD


雖然今天的 Demo 還算順利,但伺服器和主視窗寫到一半沒用上,感覺實在很可惜耶~ orz8


  1. 在 SVN 中是當成 branch ,但我想 branch 的定義應該不是這樣 :p 

  2. 訊息提示視窗、個人顯示圖片。 

  3. 伺服器、主視窗。 

  4. 傳檔案、傳訊息、對話視窗。 

  5. 我把傳檔案的子系統整合進對話視窗後,過了好久才發現要給對話視窗的按鈕加上傳檔案的事件 XD 

  6. 我用本機絕對路徑來指定圖片位置,然後傳給對方這個位址,對方當然只看得到破圖。 

  7. TWebBrower 作為訊息框,可以讓每句訊息都有不同格式,還能插入表情符號。 

  8. Demo 結束後我開啟上次的版本回想一下,才發現我上次也是伺服器端寫到一半,我又重蹈覆轍了 orz 

20080323 ~aho-corasick

昨天用 Aho-Corasick 比較慢果然是因為我使用錯誤,而今天重寫後成功了,執行時間幾乎沒有差異1,甚至在郵件量小時, Aho-Corasick 還會輸在它必須先為 Keywords 建立 tree 而花費更多時間,然而我還是很開心地改用 Aho-Corasick——因為我發現我本來使用的 for-loop + String.Contains 就是吃掉記憶體的最大兇手。之前我用 CLR Profiler 檢查時有發現在 Herbivora 中吃記憶體最嚴重的是 System.String 物件,但是我在建立字串時都有記得使用 StringBuilder ,所以一直搞不懂這是為什麼,沒想到今天拿掉 String.Contains ,改用 Aho-Corasick 後,記憶體用量馬上從 300 MB 掉到 60 MB2,所以就算 Aho-Corasick 慢一點點,我還是會用它的啦! XD

雖然根據我的需求, Aho-Corasick 跟 String.Contains 幾乎沒差,但理論上 Aho-Corasick 還是會比 String.Contains 快。今天在我將它套進 Herbivora 前先作了 benchmark ,發現速度是 Aho-Corasick ≈ String.Contains > String.IndexOf ≫ Regex , String.Contains 的速度大約比 Aho-Corasick 慢三倍3, String.IndexOf 由於還需回傳在哪裡找到,當然會比 String.Contains 慢一點, Regex 則又慢更多。

接下來聽說 Dictionary 通常會比 Hashtable 快,所以我想改用 Dictionary 。我本來就偏好使用 type-safe 的 Generic Collections ,這樣要取資料時也可以省下 type cast 的花費 :)

雖然 Dictionary 和 Hashtable 的 Method 都一樣,但行為有些不同,當我要用一個不存在的 key 來取 value 時, Hashtable 會回傳 null ,而 Dictionary 會給我 KeyNotFoundException ,我上次是用 try-catch 來處理這個 Exception ,但我沒想到 try-catch 這麼貴,昨天 Aho-Corasick 的速度會大輸,我想這也是其中一個原因…

下一步想測測看加入 q-Gram 會不會有更好的效果(用記憶體換速度 XD)


  1. 處理大量資料(57,000 封郵件)時也只快 4% 。 

  2. 這是處理大量資料時的情況,並非平時用量都這麼大。 

  3. 這只是為了方便說明而使用的某次實驗結果,實際倍數會因為 patterns 數量及 subject 長度而改變。 

20080319 ~venäyttää

昨晚睡醒後,我到 Lab 準備電子商務的報告,這章內容由於過於愛用片語,加上丟了一堆專有名詞出來而又不說明,而讓我看得一頭霧水。我這次本來不想在投影片上花太多功夫,但是 PowerPoint 2007 用著用著,我的靈感就跑出來了… XD 這次的設計是基於我的 blog 新版型,所以也命名為 De Morgan :D 不過由於這次做得沒有規劃,一邊作投影片內容、一邊修改設計,導致最後出來的投影片範本並不容易套用~

1
2
3
4

這次的主題是

  • 貴氣,但是極簡而不繁複
  • 用色和諧,和諧到副標題頁看起來完全沒有它該有的存在感 XD

首頁背景的花紋是大馬士革絲綢(Damask),因為配色而讓它看起來很中國風,感覺還滿有趣的 :p 除了 Damask 外,我最早是想找 Mucha 風格的插圖,但是類似的作品主題性都太強而無法用來當背景。


今天做到告一段落後,早上六點半我看了一下 MSN ,意外發現熊在線上,便找他一起去吃早餐 :) 外面剛下過雨,地面非常溼滑,而圖書館外面使用的是拋光過的花崗岩地板,平常看起來就很光滑,沒注意到它上面有積水,於是便跌倒了 orz 我本來以為只是皮肉之傷,沒想到似乎拉傷了一小塊肌肉,讓我整天走路都相當不便。

用完早餐後我回 Lab 繼續作投影片,等十點的堅哥 meeting ,但其實大家都沒什麼準備。我也都在忙電子商務,等到了十點多才打開 Herbivora 更新一下,在 meeting 前改程式要是改出 bug 就麻煩了,所以我不太敢在 meeting 前趕程式。我有用 SVN ,理論上是不用擔心這個問題,就算真的出現大 bug ,只要回溯到正常的舊版就能 demo ,只是新功能都寫到一半卻不能 demo 的話,我會不甘心 XD 最後由於堅哥有事,今天的 meeting 確定延期到星期五,我才開始動工,主要加上了 Spell checking 的功能(當然是套 Library ,這 Library 寫得相當漂亮,我用得很開心),還有 UI 大翻修,這下子星期五的 meeting 有著落了。

下午的 CRM 停課,我到上課前才知道,於是繼續待在 Lab 作投影片。今晚的高等計算機網路講的是比較偏概念上的東西,所以我就光看老師示範而已… 實際操作我們之前修企業網路概論時都作過了嘛~ 並不是不認真喔 XD 不過之前沒有注意到 UDP 不管是 Server 還是 Client 都可以傳送及接收,而 Server 是 Non-blocking 的,感覺用起來應該比較方便,不會有等待 response 等到當機的問題,於是我就去問老師了:「那可以兩邊都只用 Server 嗎?這樣會不會有什麼問題?」老師說這樣在傳送 binary 這種 sequence-sensitive 的資訊時可能會發生問題,雖然可以用程式端來處理,但相較之下,使用會 Blocking 的 Client 來進行這種動作,會讓程式流程比較簡單。 Blocking 讓流程限制為線性,而若用 Non-blocking ,則還需要設法處理合併封包的問題,相關實作應該非常多,至少我想斷點續傳軟體和現有的 P2P 軟體應該都有解決這個問題,只是要在作業中寫這個可能是會有些麻煩 :p

上完課後我、熊和胤一起去吉安麥當勞吃晚餐,出發前我趁著熊去火車站拿票的時候,到 7-11 想買貼布之類的東西,然而比較接近的東西只有「發燒時貼在額頭上的退燒布」,這東西日本很常見,台灣好像很少,所以我一半是為了好奇心而買下了它 XD 貼上去後感覺沒什麼差別,對我的情況來說,大概只能當作紗布包裹傷口吧!從麥當勞回宿舍後,我才買了冰塊來冰敷,我本來計畫把整隻腳掌浸在冰水裡,但是一泡下去我就覺得太冰了,泡沒幾秒就得出水緩和一下。一邊冰敷,我一邊作投影片,又作了兩個半小時才完成,而這段時間內貼布則繼續貼著,我本來預期泡完後貼布還好好的,沒想到它吸水成果凍狀,我只好再貼上另一片新的。這貼布本來設計是要讓人貼在額頭上的,所以不夠冰、延展性不好、吸水會壞掉這些問題實在不應該怪它 XD

Memory Leak in C#

Dispose() do not free memory instantly

.NET 中有自動記憶體管理機制,稱為 GC 。在下的觀念還不是很清楚,所以這篇不敢談太多背後的原理 :p

GC 讓 Programmer 不能直接管理記憶體,在我看來這應該能稱作「將記憶體管理抽象化」。 GC 並不會在「將變數設為 NULL」或「呼叫某物件的 Destructor」時馬上歸還被佔用的記憶體,而會等到某個不明的條件1發生時才啟動回收機制。但也並不是說將變數設為 NULL 或呼叫 Destructor 完全沒用,當某個條件發生時,這些變數、物件所佔用的記憶體便會被釋出。

為了確保 Desturctor 釋放出該釋放的資源,我們可以實作 IDisposable 介面:

private bool disposed = false;

protected virtual void Dispose(bool disposing)
{
    if (!disposed)
    {
        if (disposing)
        {
            // Release managed resources
        }
        // Release unmanaged resources
    }

    disposed = true;
}

public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}

~YourClass()
{
    Dispose(false);
}

實作了 IDisposable 介面之後,便可以在使用完物件時呼叫 obj.Dispose() 或者是使用 using 來將資源標記為「下次可以被釋放」。 using 的用法如下,這種寫法可以讓 Dispose() 自動地被呼叫:

using(YourClass obj = new YourClass())
{
    // Do something
}

除了 Dispose() 以外, C# 中還有一個名為 Finalize() 的 Method ,但這個 Method 無法被覆寫,所以我沒有多加研究。

不過就算照著上面作完了,記憶體仍然必須等到某個條件發生時才會釋放,如果那個條件一直沒有達到,程式就還是會發生「記憶體不足」的問題。

To GC.Collect(), or not to GC.Collect()

有許多文章說,用 GC.Collect() 可以確實地釋放出記憶體。這裡有人做了實驗,他先將記憶體塞滿,當 OutOfMemoryException 發生時,便呼叫 GC.Collect(GC.MaxGeneration) 強制 GC 釋放資源,另有一篇文章建議寫成,不過根據他測試的結果,這還必須跟 gcServer 這個屬性配合才行。組態檔的寫法如下:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <runtime>
        <gcServer enabled="false" />
    </runtime>
</configuration>

不過 gcServer 該設成 truefalse 則沒有明說。另有一篇文章提到,如果電腦的 CPU 是多核心,則 .NET 預設會採用 true ,也就是 Server Mode 。這種模式下記憶體達到釋放的門檻較高,容易發生門檻還未達到,記憶體便用罄的窘境。設成 false ,使用 Workstation Mode 可獲得改善。然而這篇文章談的是 ASP.NET ,我不確定在 Windows Form 應用程式中這個規則是否也能適用。

如果你寫的是 ASP.NET ,組態檔放在 %WINDIR%\Microsoft.NET\Framework\(版本)\Aspnet.config 。而 Windows Form 的組態檔則視執行檔檔名而定,若你的執行檔檔名是 hello.exe ,組態檔就放在同目錄下的 hello.exe.config

雖然 GC.Collect() 可能能夠解決問題,但必須注意不能過度使用,以免程式效能低落。確定要用 GC.Collect() 的話,有一篇文章提出了建議的寫法:

GC.Collect();
GC.WaitForPendingFinalizer();
GC.Collect();

  1. 對我來說是滿神秘的啦~ XD 

 1 2 3 4 Next →