[问题] 跨执行绪控制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