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

看板C_Sharp (C#)作者 (1+1≠2)時間8年前 (2016/03/28 22:15), 8年前編輯推噓3(3089)
留言92則, 4人參與, 最新討論串1/1
最近負責開發一個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 請教一下各位前輩,到底是什麼原因造成的呢? 有什麼解決方式呢?? 謝謝 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 114.27.132.126 ※ 文章網址: https://www.ptt.cc/bbs/C_Sharp/M.1459174541.A.A5C.html

03/28 22:56, , 1F
你程式中有兩個Thread,一個是程式啟動時UI的主Thread,
03/28 22:56, 1F

03/28 22:56, , 2F
另一是每次Click時產生的新的Thread,你把UserControl建
03/28 22:56, 2F

03/28 22:57, , 3F
立在新的Thread中,卻用主Thread去Invoke,就跨執行續了
03/28 22:57, 3F

03/28 23:02, , 4F
感謝yeo解惑,這個問題我也有詢問過我朋友..
03/28 23:02, 4F

03/28 23:03, , 5F
他的Form會引用不同模組,每個模組都是建立不同的
03/28 23:03, 5F

03/28 23:03, , 6F
Thread去處理,所以,建立UC和呼叫UC的不一定是同一個
03/28 23:03, 6F

03/28 23:04, , 7F
Thread,如果是這樣的話,有甚麼辦法解決呢?
03/28 23:04, 7F
※ 編輯: don750421 (114.27.132.126), 03/28/2016 23:05:00

03/28 23:18, , 8F
Control.Invoke是以該物件所屬的執行續執行委派,因此,
03/28 23:18, 8F

03/28 23:20, , 9F
只要UserControl是在主執行續下建立,執行流程中跨執行續
03/28 23:20, 9F

03/28 23:20, , 10F
時,需要涉及UI,使用UserControl.Invoke就可以了。
03/28 23:20, 10F

03/28 23:24, , 11F
其實因為你負責開發UserControl,你只要保證操作UI時是在
03/28 23:24, 11F

03/28 23:25, , 12F
UserControl所屬的執行續下執行。
03/28 23:25, 12F

03/28 23:29, , 13F
發現緒一直打錯... - -
03/28 23:29, 13F

03/28 23:39, , 14F
請教一下,您指的"只要UserControl是在主執行續下建立"
03/28 23:39, 14F

03/28 23:40, , 15F
是指Form的主執行緒?還是UC的主執行緒?
03/28 23:40, 15F

03/28 23:41, , 16F
如果是Form的主緒,有和開發Form的人員討論過..
03/28 23:41, 16F

03/28 23:42, , 17F
因為Form的主緒還會去呼叫其他的Thread處理事情...
03/28 23:42, 17F

03/28 23:42, , 18F
如果拿Form的主緒呼叫我的UC,則畫面會有停頓的情況...
03/28 23:42, 18F

03/28 23:43, , 19F
以上是指同一個,Multi UI Thread我想不是你要問的問題…
03/28 23:43, 19F

03/28 23:44, , 20F
我指的是"建立"與"操作UI"時,使用主執行續呼叫。
03/28 23:44, 20F

03/28 23:45, , 21F
如果你開一個新的執行續,裡面的工作卻是不停更新UI,自
03/28 23:45, 21F

03/28 23:46, , 22F
然會卡。
03/28 23:46, 22F
※ 編輯: don750421 (114.27.132.126), 03/28/2016 23:50:51

03/28 23:54, , 23F
因為之前顯示Log的方式,是使用DataGridview,每一筆
03/28 23:54, 23F

03/28 23:55, , 24F
Log就只需要datagridview.rows.add("xxx")加入
03/28 23:55, 24F

03/28 23:55, , 25F
想說換成TextBox簡單一些,但是開發Form的就說,之前
03/28 23:55, 25F

03/28 23:56, , 26F
呼叫方式也沒變,為什麼換成textbox就不行...
03/28 23:56, 26F

03/28 23:56, , 27F
不然,我也想說,明明我自己寫Log也都正常啊 = =|||
03/28 23:56, 27F

03/28 23:57, , 28F
和開發Form的討論過,主Thread不能拿來new 我的UC
03/28 23:57, 28F

03/28 23:58, , 29F
所以,在Sample才會new Thread 來模擬現有的情況...
03/28 23:58, 29F

03/29 00:04, , 30F
而且,UC並非只有顯示Log而已,還有其他的功能..
03/29 00:04, 30F

03/29 00:04, , 31F
我這邊只有濃縮有問題的部分寫成Sample..
03/29 00:04, 31F

03/29 00:05, , 32F
所以,除了透過主Thread建立我的UC外,還有其他方式嗎?
03/29 00:05, 32F

03/29 00:21, , 33F
這樣的要求... 那你在Contructor內不要呼叫Log操作UI,
03/29 00:21, 33F

03/29 00:21, , 34F
並且在公開呼叫的方法內,操作UI的部分都要檢查是否需要
03/29 00:21, 34F

03/29 00:21, , 35F
Invoke
03/29 00:21, 35F

03/29 00:26, , 36F
Constructor -.-,BTW,這樣的做法真的不推薦...
03/29 00:26, 36F

03/29 03:19, , 37F
我說,你們是在把事情搞複雜......
03/29 03:19, 37F

03/29 08:06, , 38F
L大的解法會是?想學習
03/29 08:06, 38F

03/29 16:16, , 39F
我是說原PO和他朋友,這種比較複雜的需求應該把系統邊界定
03/29 16:16, 39F

03/29 16:17, , 40F
好,中間的操作介面也定義出來。
03/29 16:17, 40F

03/29 18:59, , 41F
認同L大,說真的原PO若堅持要在不同執行緒下操作UI,WIN
03/29 18:59, 41F

03/29 18:59, , 42F
Form中是有Control.CheckForIllegalCrossThreadCalls可以
03/29 18:59, 42F

03/29 19:00, , 43F
攔截錯誤,但是這樣寫出來的程式,沒問題就沒問題,出問
03/29 19:00, 43F

03/29 19:01, , 44F
題時很難找到問題點。
03/29 19:01, 44F

03/29 23:41, , 45F
感謝兩位前輩回覆,今天詢問朋友的結果...
03/29 23:41, 45F

03/29 23:42, , 46F
朋友的Form介面跟我提供的Sample雷同,會有許多TabPage
03/29 23:42, 46F

03/29 23:42, , 47F
而他的TabPage是以他的MainThread來初始化...
03/29 23:42, 47F

03/29 23:43, , 48F
但是,因為我的是引用的部分,所以會是另外一個Thread
03/29 23:43, 48F

03/29 23:43, , 49F
如果都使用MainThread,變得需要先長我的TabPage,在長
03/29 23:43, 49F

03/29 23:44, , 50F
他的,這樣在畫面上會造成一些些的延遲,反之,如果先
03/29 23:44, 50F

03/29 23:44, , 51F
建他的TabPage,最後跑到我的UC時,也會稍微有一些些
03/29 23:44, 51F

03/29 23:45, , 52F
延遲的感覺...
03/29 23:45, 52F

03/29 23:46, , 53F
而且,因為我的dll是使用動態呼叫,也等於說,不一定在
03/29 23:46, 53F

03/29 23:46, , 54F
每一個場合都需要引用我的UC,所以才會另起一個Thread
03/29 23:46, 54F

03/29 23:47, , 55F
當初討論需求時,只有提到寫功能需求及傳入的參數...
03/29 23:47, 55F

03/29 23:47, , 56F
SO...這部分還在想有啥其他解法...
03/29 23:47, 56F

03/30 02:44, , 57F
你的UC一定不是單純的UI,包含了很多耗時作業
03/30 02:44, 57F

03/30 02:44, , 58F
基本上,WinForm不會違反Control就是由UI執行續建立與操作
03/30 02:44, 58F

03/30 02:45, , 59F
這個原則,否則會遇到很多麻煩。
03/30 02:45, 59F

03/30 02:47, , 60F
你要先把UI單純化:只是顯示資料與發起作業,把業務邏輯提
03/30 02:47, 60F

03/30 02:48, , 61F
到另外的類別裏,在那裡要開幾個線程隨便你。
03/30 02:48, 61F

03/30 02:49, , 62F
而不是希望建立新的的Thread來控制Control,並希望該控制
03/30 02:49, 62F

03/30 02:49, , 63F
項的工作都由這個Thread完成。
03/30 02:49, 63F

03/30 02:51, , 64F
btw,如果你是遇到UI更新頻率太高(如LOG太多)而卡死的問題
03/30 02:51, 64F

03/30 02:52, , 65F
那是需要別的手段優化。想用TextBox直接顯示LOG MESSAGE
03/30 02:52, 65F

03/30 02:52, , 66F
那個串接起來的字串長度會蠻可怕的。
03/30 02:52, 66F

03/30 05:32, , 67F
http://goo.gl/jJMqJ1 關鍵字.net cross thread delegate
03/30 05:32, 67F

03/30 23:23, , 68F
我的UC很單純,屬於被動元件,Form引用dll,UC的任何
03/30 23:23, 68F

03/30 23:24, , 69F
作都是由public的Method所觸發,像是其中一個功能就是
03/30 23:24, 69F

03/30 23:25, , 70F
Form呼叫UC,透過WebService抓取檔案,再透過UC呈現..
03/30 23:25, 70F

03/30 23:26, , 71F
沒有Hardcode任何流程,或是引用其他dll..
03/30 23:26, 71F

03/30 23:27, , 72F
至於L大提到的TextBox處理,我個人是有限制行數,超過
03/30 23:27, 72F

03/30 23:27, , 73F
2000就從前面逐行刪除,應該不至於有您所提到的問題@@
03/30 23:27, 73F

03/30 23:28, , 74F
不過,另我好奇的是,今天嘗試使用其他物件來顯示Log..
03/30 23:28, 74F

03/30 23:29, , 75F
ListBox和datagridview不會跳出跨執行緒的錯誤,為什麼
03/30 23:29, 75F

03/30 23:29, , 76F
難道是這兩種物件背後有特別做甚麼手腳嗎??
03/30 23:29, 76F

03/30 23:38, , 77F
C#的string是immutable,如果你認為重串那兩千行不會造成
03/30 23:38, 77F

03/30 23:39, , 78F
額外開銷...
03/30 23:39, 78F

03/30 23:41, , 79F
如果你是把呼叫WebService的細節直接寫在UC裡面,這就是
03/30 23:41, 79F

03/30 23:43, , 80F
把業務邏輯寫在UC裡面。不過先不討論"寫在哪裡"
03/30 23:43, 80F

03/30 23:44, , 81F
你要全部透過UC的public method控制也沒關係,但流程應該
03/30 23:44, 81F

03/30 23:46, , 82F
是:uc method-> service method
03/30 23:46, 82F

03/30 23:47, , 83F
service method done -> event -> UC ->update UI
03/30 23:47, 83F

03/30 23:48, , 84F
service method裡面可以用非同步去做,這樣UI與其執行續就
03/30 23:48, 84F

03/30 23:49, , 85F
只負責發起工作與顯示資料,而不會被業務邏輯工作佔用
03/30 23:49, 85F

03/30 23:50, , 86F
既可以優化用戶體驗,也沒有必須要用其他執行續去建控制項
03/30 23:50, 86F

03/31 00:28, , 87F
跨執行緒操作UI沒有跳出錯誤不代表你的程式是執行緒安全
03/31 00:28, 87F

03/31 00:28, , 88F
的,沒處理好這塊,會有可能發生意料之外的錯誤…你程式
03/31 00:28, 88F

03/31 00:28, , 89F
中公開的方法不需考慮被呼叫時是使用哪一個執行緒,甚至
03/31 00:28, 89F

03/31 00:28, , 90F
你在方法內要再開幾個執行緒去抓資料都可以,同步、非同
03/31 00:28, 90F

03/31 00:28, , 91F
步都可以;但在更新UI時,請回到UserControl所屬的直行
03/31 00:28, 91F

03/31 00:28, , 92F
緒叫用。
03/31 00:28, 92F
文章代碼(AID): #1M-JoDfS (C_Sharp)
文章代碼(AID): #1M-JoDfS (C_Sharp)