GUI线程安全详解(一)
作为一名TWaver Evangelist,我的工作目的就是通过与客户的交流、培训甚至现场支持等方式帮助用户将TWaver更好地应用到客户项目中,TWaver是这么一款横跨Java、Web、Flex和.NET等多种技术平台的GUI图形组件,因此我的工作内容简单的说就是:帮助客户正确使用GUI。
提到GUI线程安全,这是我最想谈也最不想谈的话题,想谈因为此问题不说明白,用户不清楚项目架构设计之初GUI最需要考虑的问题,往往后期集成测试上线时会出现灾难性的后果;不想谈因为这个话题太大了,从UI单线程的基本事件派发原理,到Swing、SWT、Flex、Silverlight、WPF甚至是Swing in EclipseRCP,Silverlight in windowsFroms,Applet in MFC等这种“杂交”的情况,这个话题一展开没有几个小时刹不住车,甚至需要陷入帮Sun、Adobe、MS向客户解释为什么Swing和Silverlight不能设计成线程安全,Flex和JavaScript为什么不提供线程的问题。
anyway,我桌面已摆着一块大面包,一大杯浓茶(我哥常鄙视我这样的喝法糟蹋了好茶叶),希望能有精力一口气把这该死的问题一次性的解释透彻。
总得来说目前几乎所有主流的GUI技术采用的都是Single UI Thread的实现方式,简单说你操作任何组件都必须在那个唯一的UI Thread里面进行,否则就会出错,这里指的也不仅仅是UI组件,大部分时候我们所指的是绑定UI组件的Model部分,因为大部分GUI技术无论是Swing/SWT的MVC,或Flex和TWaver那样的MVP,或微软在WPF和Silverlight中采用的MVP的变异MVVM,这些事件机制最终会导致你对Model层的修改实则间接的也是修改了UI组件,因此在非UI Thread的所有任何其他thread地方你绝对不能碰UI组件已经其所绑定的Model。
用TWaver的话说:你操作network,tree,table,sheet,list,chart包括已经绑定这些view的databox、element、selectionmodel、alarm、alarmmodel等都必须确保代码运行在UI Thead中。这里有个地方需要注意我指的是已经绑定了View的模型,由于TWaver的DataBox是可以用xml的文本和Serializable的二进制两种方式进行序列化,因此很多用户会把twaver的databox数据构建放在后台进行,包括TWaver Web整个模型是在J2EE后台容器操作,这种情况下你只要保证单线程操作即可,并不要求这个线程必须是UI Thread,因为你还没有绑定View因此没有UI Thread线程安全的限制。
有些人问为什么要有这些限制,为什么TWaver不能设计成现场安全的,其实我们的回答和Sun(现在的Oracle)的解释是一致的Why did we implement Swing this way?,同样你也可以找到Why did we implement SWT,Flex,WindowsForms,Silverlight,WPF….this way的类似解释的文章,并不是我们逃脱责任将痛苦留给客户,而是因为我们认为多线程的GUI框架非常困难,更不利于用户的开发调试,甚至由于需要进行的各种同步保护反而降低了整体GUI交互效果。
简单说一个存着number元素的list让你求sum,如果有多线程并发在你for循环的时候动态修改你怎么办,你会说加上lock锁定这个list不就得了,如果这个list非常长需要运行很长时间那岂不其他人都被你堵住了,你会想的想java.util.concurrent的实现那样分成更多的块来操作,老大你看看ConcurrentHashMap的一千多行代码才仅仅解决了map问题,你还会说看看微软C#4的强大的并行处理Parallel.ForEach函数,即使是这个也仅仅解决了无状态和没有太多耦合逻辑的运算分解,对于复杂的GUI耦合逻辑目前还没有灵丹妙药,Parallel.ForEach也是需要让你修改代码的,现在的本子动不动就配上4个8个CPU,但跑在上面的GUI程序性能不会比当CPU的强,因为需要GUI密集运算工作时依然只有一个CPU在工作。
这些年并行计算呼声很高,google这样的巨人已经让我们老百姓感受到了并行计算的优势,但回到GUI我们依然无法轻松、廉价和透明的体验到并行的好处,也许有那么一天会到来,但目前阶段我并不渴望,以目前的语言类库基础如果采用并发的方式只会让我们的调试开发更痛苦,当然最重要的是现在的主要瓶颈已经不是GUI的组件了,而是数据DATA,从海量的数据中查询获取到你需要的DATA这个过程才是瓶颈,良好的GUI框架设计不应该将数据的获取与界面的呈现耦合得不可分离,就像TWaver可以秒级的加载万甚至10万以上的图元,但最大的瓶颈是用户如何更短的时间去获取和传输这些数据。
再看看现在热卖得脱销iphone4(一万多甚至最高两万二的天价让我想起当年的大哥大时代)和IPad,对应这样的硬件配置用于浏览网页,受发邮件,听歌看碟,玩玩小游戏已经搓搓有余了,大家抱怨的是信号门,抱怨的是不堪重负的AT&T网络,题外话Cocoa同样也不是线程安全的哦:
因此现阶段我们唯一能做的就是老老实实遵守Single UI Thread设计带来的限制,这个Thread有很多叫法,在Swing里面称之为EDT(Event Dispatcher Thread),以下的文章中我们都会简称之EDT。