TIPS-About UI Thread Limitation

這是一個程式"中鳥"開發Windows Form要面對的問題...

Windows Form裡的Threading有些討厭的限制。菜鳥還處於天真無邪的Single Thread打天下階段,渾然不知它的險惡;而老鳥早就吃過苦頭,熟知要如何對付它,所以也不畏懼它的刁難。而剛開始學會在Windows Form中展現Multi-threading威力的中鳥,一起步多半要先闖過這一關。

用一個最簡單的例子來說明好: 我寫了一個Windows Form,放了一個label1一個button1, 在button1_Click事件另外建立一個Thread呼叫chgLabel(text)修改label1的Text屬性:

        private void button1_Click(object sender, EventArgs e)
        {
            Thread trd = new Thread(new ThreadStart(jobBad));
            trd.Start();
        }
 
        private void jobBad() 
        {
            chgLabel("Wrong way.");
        }
 
        private void chgLabel(string text)
        {
            label1.Text = text;
        }

程式碼很簡單,看起來應該沒什麼問題吧?

如果你這麼想,表示你在Windows Form的世界還涉世未深...

這段程式觸犯了Windows Form裡的一條鐵律: 在非UI Thread裡更動了UI的內容!

從沒用C++, Windows SDK角度探索Windows Application Model的我,並不知道它的細節原由,但歷經多次慘痛的經驗教訓,我學到一點: 如果你另外開了一條Thread,程式碼如會變更到Windows Form上的Control內容,請記得繞道而行。在上述的例子中,我們很明確地修改了Label的內容,但有時程式碼會在你不知情的情況下更動了UI元素。最典型的例子是DataGrid Bind到一個DataTable,而我們在另一條Thread中表面上只更改了DataTable的內容,並沒有修改UI上的Control;但由於Data Binding的關係,一更動DataTable就會間接觸發了修改DataGrid顯示的事件,進而違反了在非UI Thread修改UI內容的規則。

記得在.NET 1.1時代,違反這條規則並非絕對會出錯,而是偶爾冒出Null Reference Exception讓你丈二金剛摸不著腦袋,完全不知自己犯了什麼天條遭此報應。.NET 2.0比較仁道一點,會很明確地彈出以下的訊息:
Invalid Operation Exception:
Cross-thread operation not valid: Control 'label1' accessed from a thread other than the thread it was created on.

這張罰單把違規事由寫得清清楚楚,想當初由Null Reference去追出問題出在UI Thread限制就讓我吃盡苦頭,由.NET 2.0入門的朋友算是幸福多了。

Good! 知道問題後,要怎麼修改? 官方的標準答案是宣告一個deleate, 再用InvokeBeginInvoke來觸發它,這樣可以強迫Windows切回UI Thread來執行程式

        private void button1_Click(object sender, EventArgs e)
        {
            Thread trd = new Thread(new ThreadStart(jobGood));
            trd.Start();
        }
 
        private void chgLabel(string text)
        {
            label1.Text = text;
        }
 
        delegate void ChgLabelHandler(string text);
        private void jobGood()
        {
            this.Invoke(new ChgLabelHandler(chgLabel), "Correct way.");
        }

還有一些進階的課題,例如: 某些Code可能被UI Thread呼叫,也可能被非UI Thread執行,所以可以用InvokeRequired來偵測,決定要直接變更UI內容或是使用Invoke,這裡就先不要搞得太複雜,以免嚇到大家。先記住一點,當你遇到"Cross-thread operation not valid",要知道發生了什麼事以及該怎麼辦,就夠了。

歡迎推文分享:
Published 30 September 2007 10:33 AM 由 Jeffrey
Filed under: ,


意見

# kennyshu said on 22 October, 2007 01:46 PM

我在菜鳥的時候就遇到了這個問題(現在還是菜) >"<

之前用.Net 1.1的時候(也就是VS2003)直接改UI上的control都沒有問題,升級到2.0後就出現一堆問題了,當時真的覺得2.0比1.1還不好用…

研究了好久並且請教一些先進之後,目前我是用BeginInvoke + MethodInvoker來解決這個問題。

# Robert said on 24 October, 2007 04:32 AM

除了比較複雜的狀況, 我都用BackgroupWorker這個Component. 感覺上不用傷腦筋, Code也比較好看...ㄟ...不過可能也是我比較懶惰吧...哈

# Jeffrey said on 24 October, 2007 04:55 AM

To Robert, 沒錯,.NET 2.0的BackgroundWorker又是一個"傑克,這真的是太神奇了"級的新發明,事實上我也正在寫一篇相關的文章。

現在的程式開發人員需要懂的東西愈來愈少,卻可以做出同樣複雜的東西,真是幸福,但相對也少了深入了解背後原理的機會,某種角度來說也是種不幸吧?? (謎之聲: 只有你這種愛鑽牛角尖的賤骨頭才會這樣想吧? 哈!)

# Ken Wu said on 20 November, 2007 05:06 PM

錯別字deleate -> delegate

推薦幾篇2002~2004年相關的官方文章:

msdn2.microsoft.com/.../ms998490.aspx

msdn2.microsoft.com/.../ms996402.aspx

msdn2.microsoft.com/.../ms996483.aspx

# SGY said on 10 April, 2008 09:10 AM

Test .

Runtime 可執行Change UI !! (真的改變了)

Debug Mode: 會Error!!

# SGY said on 11 April, 2008 09:31 AM

When App 在 TaskTray  

  this.Visual =false

When App 不在 TaskTray  

  this.Visual =false

Use thread Update UI時要小心

       private void button1_Click(object sender, EventArgs e)        

{            

Thread trd = new Thread(new ThreadStart(jobGood));            

trd.Start();        

}        

private void chgLabel(string text)        

{            

label1.Text = text;        

}        

delegate void ChgLabelHandler(string text);        

private void jobGood()        

{            

this.Invoke(new ChgLabelHandler(chgLabel), "Correct way.");        

}

改良一下

private void jobGood()        

{

           if (this.Visible)

           {

              this.Invoke(new ChgLabelHandler(chgLabel), "Correct way.");  

           }

           else

           {

               chgLabel("Correct way.");

           }          

}

這樣就完美了 ^^

By:SGY

# Jeffrey said on 11 April, 2008 11:06 AM

to SGY, 謝謝你的補充,我有另一篇較完整的探討文章,也歡迎你參考指教。

blog.darkthread.net/.../better-winform-ui.aspx

# Samuel said on 13 January, 2009 02:14 AM

請問在 .net C++ 要如何做出此功能

# Pao said on 04 March, 2009 06:30 AM

謝謝!

寫的簡潔有利

對我這菜鳥幫助頗大

感恩

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 

請輸入以上的數字:

搜尋

Go

<September 2007>
SunMonTueWedThuFriSat
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456
 
RSS
【工商服務】
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


BlogLook Score and Rank

Syndication