作者don750421 (1+1≠2)
看板C_Sharp
标题[问题] 跨执行绪控制UI失败(附Code)
时间Mon Mar 28 22:15:30 2016
最近负责开发一个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://webptt.com/cn.aspx?n=bbs/C_Sharp/M.1459174541.A.A5C.html
1F:推 yeo1987: 你程式中有两个Thread,一个是程式启动时UI的主Thread, 03/28 22:56
2F:→ yeo1987: 另一是每次Click时产生的新的Thread,你把UserControl建 03/28 22:56
3F:→ yeo1987: 立在新的Thread中,却用主Thread去Invoke,就跨执行续了 03/28 22:57
4F:→ don750421: 感谢yeo解惑,这个问题我也有询问过我朋友.. 03/28 23:02
5F:→ don750421: 他的Form会引用不同模组,每个模组都是建立不同的 03/28 23:03
6F:→ don750421: Thread去处理,所以,建立UC和呼叫UC的不一定是同一个 03/28 23:03
7F:→ don750421: Thread,如果是这样的话,有甚麽办法解决呢? 03/28 23:04
※ 编辑: don750421 (114.27.132.126), 03/28/2016 23:05:00
8F:推 yeo1987: Control.Invoke是以该物件所属的执行续执行委派,因此, 03/28 23:18
9F:→ yeo1987: 只要UserControl是在主执行续下建立,执行流程中跨执行续 03/28 23:20
10F:→ yeo1987: 时,需要涉及UI,使用UserControl.Invoke就可以了。 03/28 23:20
11F:→ yeo1987: 其实因为你负责开发UserControl,你只要保证操作UI时是在 03/28 23:24
12F:→ yeo1987: UserControl所属的执行续下执行。 03/28 23:25
13F:→ yeo1987: 发现绪一直打错... - - 03/28 23:29
14F:→ don750421: 请教一下,您指的"只要UserControl是在主执行续下建立" 03/28 23:39
15F:→ don750421: 是指Form的主执行绪?还是UC的主执行绪? 03/28 23:40
16F:→ don750421: 如果是Form的主绪,有和开发Form的人员讨论过.. 03/28 23:41
17F:→ don750421: 因为Form的主绪还会去呼叫其他的Thread处理事情... 03/28 23:42
18F:→ don750421: 如果拿Form的主绪呼叫我的UC,则画面会有停顿的情况... 03/28 23:42
19F:→ yeo1987: 以上是指同一个,Multi UI Thread我想不是你要问的问题… 03/28 23:43
20F:→ yeo1987: 我指的是"建立"与"操作UI"时,使用主执行续呼叫。 03/28 23:44
21F:→ yeo1987: 如果你开一个新的执行续,里面的工作却是不停更新UI,自 03/28 23:45
22F:→ yeo1987: 然会卡。 03/28 23:46
※ 编辑: don750421 (114.27.132.126), 03/28/2016 23:50:51
23F:→ don750421: 因为之前显示Log的方式,是使用DataGridview,每一笔 03/28 23:54
24F:→ don750421: Log就只需要datagridview.rows.add("xxx")加入 03/28 23:55
25F:→ don750421: 想说换成TextBox简单一些,但是开发Form的就说,之前 03/28 23:55
26F:→ don750421: 呼叫方式也没变,为什麽换成textbox就不行... 03/28 23:56
27F:→ don750421: 不然,我也想说,明明我自己写Log也都正常啊 = =||| 03/28 23:56
28F:→ don750421: 和开发Form的讨论过,主Thread不能拿来new 我的UC 03/28 23:57
29F:→ don750421: 所以,在Sample才会new Thread 来模拟现有的情况... 03/28 23:58
30F:→ don750421: 而且,UC并非只有显示Log而已,还有其他的功能.. 03/29 00:04
31F:→ don750421: 我这边只有浓缩有问题的部分写成Sample.. 03/29 00:04
32F:→ don750421: 所以,除了透过主Thread建立我的UC外,还有其他方式吗? 03/29 00:05
33F:→ yeo1987: 这样的要求... 那你在Contructor内不要呼叫Log操作UI, 03/29 00:21
34F:→ yeo1987: 并且在公开呼叫的方法内,操作UI的部分都要检查是否需要 03/29 00:21
35F:→ yeo1987: Invoke 03/29 00:21
36F:→ yeo1987: Constructor -.-,BTW,这样的做法真的不推荐... 03/29 00:26
37F:→ Litfal: 我说,你们是在把事情搞复杂...... 03/29 03:19
38F:推 yeo1987: L大的解法会是?想学习 03/29 08:06
39F:→ Litfal: 我是说原PO和他朋友,这种比较复杂的需求应该把系统边界定 03/29 16:16
40F:→ Litfal: 好,中间的操作介面也定义出来。 03/29 16:17
41F:→ yeo1987: 认同L大,说真的原PO若坚持要在不同执行绪下操作UI,WIN 03/29 18:59
42F:→ yeo1987: Form中是有Control.CheckForIllegalCrossThreadCalls可以 03/29 18:59
43F:→ yeo1987: 拦截错误,但是这样写出来的程式,没问题就没问题,出问 03/29 19:00
44F:→ yeo1987: 题时很难找到问题点。 03/29 19:01
45F:→ don750421: 感谢两位前辈回覆,今天询问朋友的结果... 03/29 23:41
46F:→ don750421: 朋友的Form介面跟我提供的Sample雷同,会有许多TabPage 03/29 23:42
47F:→ don750421: 而他的TabPage是以他的MainThread来初始化... 03/29 23:42
48F:→ don750421: 但是,因为我的是引用的部分,所以会是另外一个Thread 03/29 23:43
49F:→ don750421: 如果都使用MainThread,变得需要先长我的TabPage,在长 03/29 23:43
50F:→ don750421: 他的,这样在画面上会造成一些些的延迟,反之,如果先 03/29 23:44
51F:→ don750421: 建他的TabPage,最後跑到我的UC时,也会稍微有一些些 03/29 23:44
52F:→ don750421: 延迟的感觉... 03/29 23:45
53F:→ don750421: 而且,因为我的dll是使用动态呼叫,也等於说,不一定在 03/29 23:46
54F:→ don750421: 每一个场合都需要引用我的UC,所以才会另起一个Thread 03/29 23:46
55F:→ don750421: 当初讨论需求时,只有提到写功能需求及传入的参数... 03/29 23:47
56F:→ don750421: SO...这部分还在想有啥其他解法... 03/29 23:47
57F:→ Litfal: 你的UC一定不是单纯的UI,包含了很多耗时作业 03/30 02:44
58F:→ Litfal: 基本上,WinForm不会违反Control就是由UI执行续建立与操作 03/30 02:44
59F:→ Litfal: 这个原则,否则会遇到很多麻烦。 03/30 02:45
60F:→ Litfal: 你要先把UI单纯化:只是显示资料与发起作业,把业务逻辑提 03/30 02:47
61F:→ Litfal: 到另外的类别里,在那里要开几个线程随便你。 03/30 02:48
62F:→ Litfal: 而不是希望建立新的的Thread来控制Control,并希望该控制 03/30 02:49
63F:→ Litfal: 项的工作都由这个Thread完成。 03/30 02:49
64F:→ Litfal: btw,如果你是遇到UI更新频率太高(如LOG太多)而卡死的问题 03/30 02:51
65F:→ Litfal: 那是需要别的手段优化。想用TextBox直接显示LOG MESSAGE 03/30 02:52
66F:→ Litfal: 那个串接起来的字串长度会蛮可怕的。 03/30 02:52
68F:→ don750421: 我的UC很单纯,属於被动元件,Form引用dll,UC的任何 03/30 23:23
69F:→ don750421: 作都是由public的Method所触发,像是其中一个功能就是 03/30 23:24
70F:→ don750421: Form呼叫UC,透过WebService抓取档案,再透过UC呈现.. 03/30 23:25
71F:→ don750421: 没有Hardcode任何流程,或是引用其他dll.. 03/30 23:26
72F:→ don750421: 至於L大提到的TextBox处理,我个人是有限制行数,超过 03/30 23:27
73F:→ don750421: 2000就从前面逐行删除,应该不至於有您所提到的问题@@ 03/30 23:27
74F:→ don750421: 不过,另我好奇的是,今天尝试使用其他物件来显示Log.. 03/30 23:28
75F:→ don750421: ListBox和datagridview不会跳出跨执行绪的错误,为什麽 03/30 23:29
76F:→ don750421: 难道是这两种物件背後有特别做甚麽手脚吗?? 03/30 23:29
77F:→ Litfal: C#的string是immutable,如果你认为重串那两千行不会造成 03/30 23:38
78F:→ Litfal: 额外开销... 03/30 23:39
79F:→ Litfal: 如果你是把呼叫WebService的细节直接写在UC里面,这就是 03/30 23:41
80F:→ Litfal: 把业务逻辑写在UC里面。不过先不讨论"写在哪里" 03/30 23:43
81F:→ Litfal: 你要全部透过UC的public method控制也没关系,但流程应该 03/30 23:44
82F:→ Litfal: 是:uc method-> service method 03/30 23:46
83F:→ Litfal: service method done -> event -> UC ->update UI 03/30 23:47
84F:→ Litfal: service method里面可以用非同步去做,这样UI与其执行续就 03/30 23:48
85F:→ Litfal: 只负责发起工作与显示资料,而不会被业务逻辑工作占用 03/30 23:49
86F:→ Litfal: 既可以优化用户体验,也没有必须要用其他执行续去建控制项 03/30 23:50
87F:→ yeo1987: 跨执行绪操作UI没有跳出错误不代表你的程式是执行绪安全 03/31 00:28
88F:→ yeo1987: 的,没处理好这块,会有可能发生意料之外的错误…你程式 03/31 00:28
89F:→ yeo1987: 中公开的方法不需考虑被呼叫时是使用哪一个执行绪,甚至 03/31 00:28
90F:→ yeo1987: 你在方法内要再开几个执行绪去抓资料都可以,同步、非同 03/31 00:28
91F:→ yeo1987: 步都可以;但在更新UI时,请回到UserControl所属的直行 03/31 00:28
92F:→ yeo1987: 绪叫用。 03/31 00:28