[問題] 跨執行緒控制UI失敗(附Code)

作者: don750421 (1+1≠2)   2016-03-28 22:15:30
最近負責開發一個dll,裡面包含了一個UserControl(以下簡稱 UC)
這個UC含有許多功能,所以,UC有錯誤時,希望能夠透過本身的介面顯示出來。
因此這個UC會有一個Rirchtextbox 來顯示UC的log並寫成file。
另一位朋友,則是負責開發Form,並把我的UC 加入到他的Form。
但問題發生了,當他將我的UC初始化完成後,Add UC到他的Form。
系統卻拋出跨執行緒處理異常的錯誤 ==> 如右圖 http://i.imgur.com/BlIKUOm.jpg
我和朋友嘗試的許多方式,還是會出現錯誤。而且,如果執行
Richtextbox.Text = "aaa"; ==> 不會出現錯誤
Richtextbox.AppendText("aaa"); ==> 拋出跨執行緒錯誤
嘗試使用RichtextBox和TextBox 都是相同錯誤。...
附上簡單寫的Code (Mega空間) => https://4fun.tw/IDMo
原始路徑:
https://mega.nz/#!6AoxHTAJ!DWJmWJhT9t7NhesNizTZVPZawbrByImnVM2h_eZn87k
請教一下各位前輩,到底是什麼原因造成的呢? 有什麼解決方式呢??
謝謝
作者: yeo1987 (Archie)   2016-03-28 22:56:00
你程式中有兩個Thread,一個是程式啟動時UI的主Thread,另一是每次Click時產生的新的Thread,你把UserControl建立在新的Thread中,卻用主Thread去Invoke,就跨執行續了
作者: don750421 (1+1≠2)   2016-03-28 23:02:00
感謝yeo解惑,這個問題我也有詢問過我朋友..他的Form會引用不同模組,每個模組都是建立不同的Thread去處理,所以,建立UC和呼叫UC的不一定是同一個Thread,如果是這樣的話,有甚麼辦法解決呢?
作者: yeo1987 (Archie)   2016-03-28 23:18:00
Control.Invoke是以該物件所屬的執行續執行委派,因此,只要UserControl是在主執行續下建立,執行流程中跨執行續時,需要涉及UI,使用UserControl.Invoke就可以了。其實因為你負責開發UserControl,你只要保證操作UI時是在UserControl所屬的執行續下執行。發現緒一直打錯... - -
作者: don750421 (1+1≠2)   2016-03-28 23:39:00
請教一下,您指的"只要UserControl是在主執行續下建立"是指Form的主執行緒?還是UC的主執行緒?如果是Form的主緒,有和開發Form的人員討論過..因為Form的主緒還會去呼叫其他的Thread處理事情...如果拿Form的主緒呼叫我的UC,則畫面會有停頓的情況...
作者: yeo1987 (Archie)   2016-03-28 23:43:00
以上是指同一個,Multi UI Thread我想不是你要問的問題…我指的是"建立"與"操作UI"時,使用主執行續呼叫。如果你開一個新的執行續,裡面的工作卻是不停更新UI,自然會卡。
作者: don750421 (1+1≠2)   2016-03-28 23:54:00
因為之前顯示Log的方式,是使用DataGridview,每一筆Log就只需要datagridview.rows.add("xxx")加入想說換成TextBox簡單一些,但是開發Form的就說,之前呼叫方式也沒變,為什麼換成textbox就不行...不然,我也想說,明明我自己寫Log也都正常啊 = =|||和開發Form的討論過,主Thread不能拿來new 我的UC所以,在Sample才會new Thread 來模擬現有的情況...而且,UC並非只有顯示Log而已,還有其他的功能..我這邊只有濃縮有問題的部分寫成Sample..所以,除了透過主Thread建立我的UC外,還有其他方式嗎?
作者: yeo1987 (Archie)   2016-03-29 00:21:00
這樣的要求... 那你在Contructor內不要呼叫Log操作UI,並且在公開呼叫的方法內,操作UI的部分都要檢查是否需要InvokeConstructor -.-,BTW,這樣的做法真的不推薦...
作者: Litfal (Litfal)   2016-03-29 03:19:00
我說,你們是在把事情搞複雜......
作者: yeo1987 (Archie)   2016-03-29 08:06:00
L大的解法會是?想學習
作者: Litfal (Litfal)   2016-03-29 16:16:00
我是說原PO和他朋友,這種比較複雜的需求應該把系統邊界定好,中間的操作介面也定義出來。
作者: yeo1987 (Archie)   2016-03-29 18:59:00
認同L大,說真的原PO若堅持要在不同執行緒下操作UI,WINForm中是有Control.CheckForIllegalCrossThreadCalls可以攔截錯誤,但是這樣寫出來的程式,沒問題就沒問題,出問題時很難找到問題點。
作者: don750421 (1+1≠2)   2016-03-29 23:41:00
感謝兩位前輩回覆,今天詢問朋友的結果...朋友的Form介面跟我提供的Sample雷同,會有許多TabPage而他的TabPage是以他的MainThread來初始化...但是,因為我的是引用的部分,所以會是另外一個Thread如果都使用MainThread,變得需要先長我的TabPage,在長他的,這樣在畫面上會造成一些些的延遲,反之,如果先建他的TabPage,最後跑到我的UC時,也會稍微有一些些延遲的感覺...而且,因為我的dll是使用動態呼叫,也等於說,不一定在每一個場合都需要引用我的UC,所以才會另起一個Thread當初討論需求時,只有提到寫功能需求及傳入的參數...SO...這部分還在想有啥其他解法...
作者: Litfal (Litfal)   2016-03-30 02:44:00
你的UC一定不是單純的UI,包含了很多耗時作業基本上,WinForm不會違反Control就是由UI執行續建立與操作這個原則,否則會遇到很多麻煩。你要先把UI單純化:只是顯示資料與發起作業,把業務邏輯提到另外的類別裏,在那裡要開幾個線程隨便你。而不是希望建立新的的Thread來控制Control,並希望該控制項的工作都由這個Thread完成。btw,如果你是遇到UI更新頻率太高(如LOG太多)而卡死的問題那是需要別的手段優化。想用TextBox直接顯示LOG MESSAGE那個串接起來的字串長度會蠻可怕的。
作者: cid1979 (cid)   2016-03-30 05:32:00
http://goo.gl/jJMqJ1 關鍵字.net cross thread delegate
作者: don750421 (1+1≠2)   2016-03-30 23:23:00
我的UC很單純,屬於被動元件,Form引用dll,UC的任何作都是由public的Method所觸發,像是其中一個功能就是Form呼叫UC,透過WebService抓取檔案,再透過UC呈現..沒有Hardcode任何流程,或是引用其他dll..至於L大提到的TextBox處理,我個人是有限制行數,超過2000就從前面逐行刪除,應該不至於有您所提到的問題@@不過,另我好奇的是,今天嘗試使用其他物件來顯示Log..ListBox和datagridview不會跳出跨執行緒的錯誤,為什麼難道是這兩種物件背後有特別做甚麼手腳嗎??
作者: Litfal (Litfal)   2016-03-30 23:38:00
C#的string是immutable,如果你認為重串那兩千行不會造成額外開銷...如果你是把呼叫WebService的細節直接寫在UC裡面,這就是把業務邏輯寫在UC裡面。不過先不討論"寫在哪裡"你要全部透過UC的public method控制也沒關係,但流程應該是:uc method-> service methodservice method done -> event -> UC ->update UIservice method裡面可以用非同步去做,這樣UI與其執行續就只負責發起工作與顯示資料,而不會被業務邏輯工作佔用既可以優化用戶體驗,也沒有必須要用其他執行續去建控制項
作者: yeo1987 (Archie)   2016-03-31 00:28:00
跨執行緒操作UI沒有跳出錯誤不代表你的程式是執行緒安全的,沒處理好這塊,會有可能發生意料之外的錯誤…你程式中公開的方法不需考慮被呼叫時是使用哪一個執行緒,甚至你在方法內要再開幾個執行緒去抓資料都可以,同步、非同步都可以;但在更新UI時,請回到UserControl所屬的直行緒叫用。

Links booklink

Contact Us: admin [ a t ] ucptt.com