TWaver - 專注UI技術

          http://twaver.servasoft.com/
          posts - 171, comments - 191, trackbacks - 0, articles - 2
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          深入淺出GUI線程安全(二)

          Posted on 2010-09-01 22:50 TWaver 閱讀(1321) 評論(0)  編輯  收藏

          上一篇之后應該以后可以少解釋為什么要考慮線程安全的了,這篇的重點是如何保證GUI線程安全。

          電信網管里最常見的場景莫過于后來來了個告警需要更新界面網元,很多TWaver客戶得到后臺來的告警信息后很自然的去調用DataBox里Element的AlarmState,或者通過Alarm對象驅動AlarmModel了,這時問題就來了,不管你用的JMS,COBOL,MQ或者是自己起的Socket連接給你的message的thread絕對不是EDT的線程中,所以如果你直接在得到消息后更新UIModel或者UI就是違背了GUI的線程安全原則。

          這種情況下你唯一能做的就是將你要做的任務注冊到EDT的事件派發隊列里面,如果用Swing可以調用SwingUtilities.invokeAndWait或invokeLater,用SWT可以調用Display.asyncExec或syncExec,用Silverlight和WPF可以調用Dispatcher.BeginInvoke,這樣通過注冊Runnable或者Action之類的任務,等待著EDT在它有空處理你的時候就會調用你的注冊的Runnable或Action執行動作了,這時候在里面就是這個唯一的UI Thread在運行了,這里面你大可放心的去操作UI或綁定UI的Model,唯一需要注意的是UI Thread全局就一個在工作,當你在執行時用戶界面是不會得到任何響應的,因此你必須快速處理別站著茅坑***,假設你處理了半分鐘,那對用戶來說他會告訴你死機了半分鐘。(有人留意到我怎么沒提到Flex和JavaScript的調用函數,這兩個UI平臺比較特殊,下篇我再細講)。

          TWaver的Demo的有很多地方都有模擬實時更新的應用,大家可以參考一下

          除了調用上面提到的一些比較常規的invoke方式外你還可以考慮用Timer,SWT里面你可以調用Display.timerExec,Swing里面javax.swing.Timer,注意我這里指的不是java.util.Timer,用util的Timer那基本我們前面說的做的都白費了,javax.swing包下的那個Timer才能保證回調時的Thread是EDT,同樣.NET下有起碼四五個叫Timer的兄弟,System.Windows.Threading.DispatcherTimer這個我比較常用,我估計笨到能去調用System.Web.UI.Timer的人估計沒興趣在看我這枯草的全是代碼的文章吧,如果你想深入了解微軟問什么搞了這么多個Timer來折磨程序員的話你可以讀讀這篇這篇,這里不得的贊嘆微軟一下,大家都這家伙夠啥都能搞成恐龍級,不過這家伙養的那只恐龍都有浩如煙海的詳盡文檔將每個細胞描述的滴水不漏,而且幾乎不見文檔語法帶個錯字的,這點很少有公司能做的足夠好的,即使是google更不用說adobe了,不過文檔做不到完善也無妨,只要產品質量過硬也無傷大雅,所以大家要是看到我文章的錯別字還請海涵,kao,扯遠了回到正題吧。

          剛才上面還提到一個注意點就是在EDT里面不要執行太長時間否則用戶體驗很差,這點往往就是大部分不熟悉傳統GUI開發者的錯誤偏見:CS很重很慢BS很輕量是趨勢。產生這種偏見的根源我覺得是這樣的:不管開發desktop還是web甚至是逐漸流行的mobile和tablet應用程序,要達到良好的用戶體驗都需要深入了解你所使用的GUI平臺技術,但在目前這種浮躁的、項目型的、短期利益驅動的時代,你很難想象可以為一個項目中標后立刻在短期內擴展幾十上百號人,或者租借大批今天做你們公司項目明天做其他公司項目的人員,而且幾乎都是既懂得AIX下Oracle的tuning,又能充當美工ps兩下,甚至對項目管理工具游刃有余的可以臨時充當PM的萬精油類型。

          這些年我支持了可以說上百家的客戶的確有見不僅可以“萬”而且可以“精”者,但這畢竟是少數大部分都是一知半解的就開始沖鋒上陣了,因此固然非常容易很多沒有經驗的程序員在EDT里面做一大堆工作,甚至是通訊查詢數據庫的工作都搞在里面了,如果能不慢那才是奇跡了,而Web的程序員里面沒經驗的比例其實和desktop類型的是基本一樣的(爭辯這個好比爭辯男人聰明還是女人聰明,其實據統計學看絕頂聰明的、一般的已經弱智的比例基本是一樣的),只不過Web的應用大部分獲取數據在后臺直接進行了,推到前臺的基本就是HTML的呈現UI信息了,所以是慢在后臺了用戶看不見而已,用戶打開一個頁面很慢他是會說服務器很慢啊,他絕對不會說瀏覽器的HTML渲染解析引擎好慢。俺以前也經歷過個龐大的項目,以前用PB做客戶端,忘了是SqlServer97還是2000,跑在多年前很普通的Windows服務器上,就這么樣配置架構的系統很好的能快速響應的服務于一個城市多年,后來我們這些家伙就來了,換成了HP小機(一款新出的機型,轉個JDK都得到他們官方網站專門定做的JDK才能裝的上,后來遇到不知道多少的咨詢嘗試我們才活了下來),竟然采用了Oracle的J2EE容器(六七年前大家只知道WebLogic、WebSphere和JBoss,一個三四十號人的J2EE開發團隊沒人見過Oracle的J2EE容器是個什么樣的家伙,后來果不其然這玩意兒沒少折騰我們),說實話這個系統如果按當初老系統的服務器配置根本就跑不了任何應用,完全靠的是驚人的后臺N多CPU,-Xmx$$G,加上不可思議的IO能力才解決了問題,可以說是靠錢堆出了勉強可以的用戶體驗。

          上面這個案例我只想說明不管什么應用程序要有好的應用體驗一定要將數據的獲取和UI的呈現邏輯分離,否則Web應用可以通過加大后臺服務器配置哪怕你的SQL寫的效率低點也有補救的可能(當然優化代碼和SQL才是根本這個我就不爭辯了),但desktop程序如果在UI Thread搞入一大堆密集預算或者長期等待的邏輯操作的話,那基本無藥可救了,哪怕你讓客戶配置上100核的CPU也只有一個Thread在工作。

          講了這么多大道理,讓我們上代碼吧,我這里強烈推薦大家閱讀以下TWaver的FileTreeDemo,以下兩個抓圖分別是TWaver Java和TWaver .NET例子的抓圖

           1public void loadChildren(final FileElement element) {
           2    final String oldIconURL = element.getIconURL();
           3    element.setIcon(loadingIcon);
           4
           5    Thread t = new Thread(new Runnable(){
           6        public void run() {
           7
           8            // fileSystemView.getFiles may cost a lot of time,
           9            // so we create all element in NOT swing thead.
          10            File file = element.getFile();
          11            File[] files = fileSystemView.getFiles(file, true);
          12            final List children = new ArrayList();
          13            if (files != null && files.length > 0{
          14                for (int i = 0; i < files.length; ++i) {
          15                    children.add(createElement(files[i]));
          16                }

          17            }

          18
          19            // when you want to add element to the box that
          20            // already connected with swing component like tree,
          21            // network, table, sheet, etc. you should do this
          22            // job in swing thread.
          23            SwingUtilities.invokeLater(new Runnable(){
          24                public void run() {
          25                    box.addElements(children, element);
          26                    element.setLoaded(true);
          27                    element.setIcon(oldIconURL);
          28                    decreaseCounter();
          29                }

          30            }
          );
          31        }

          32    }
          );
          33
          34    increaseCounter();
          35    t.start();
          36}

          以上代碼已經說明了我想說的重點,也就是當你expand目錄節點時TWaver才需要你動態加載子目錄數據,expend時回調的loadChildren事件就是在EDT中,而讀取磁盤文件是耗時的操作,如果你直接在EDT里面查詢子目錄并且創建對于的element信息,肯定界面需要堵塞住知道磁盤子目錄和文件讀完界面才有反應,TWaver Demo的實現起了個線程去做獲取數據的工作,這里注意到element其實是在普通線程創建的,這里沒有問題因為其還未添加到box,還不會影響到view上,而box已經和tree綁定了,因此對box的操作必然會影響tree的更新,因此數據獲并且創建成element對象之后才通過SwingUtilities.invokeLater將element添加給box,這樣你才有肯呢個看到右邊不斷滾動的tracing信息。

          面包啃完了,濃茶被我沖泡得像白開水了,先睡了明天繼續


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 合山市| 长岭县| 东乌珠穆沁旗| 精河县| 肇州县| 哈密市| 韶山市| 桃园市| 五峰| 肇东市| 武胜县| 华宁县| 新源县| 特克斯县| 无棣县| 新津县| 周口市| 大庆市| 西丰县| 侯马市| 伊宁市| 新和县| 宣威市| 开封县| 湖州市| 武威市| 饶河县| 梓潼县| 吉水县| 康保县| 新丰县| 元江| 安仁县| 北宁市| 诸城市| 广河县| 阜康市| 新竹市| 澎湖县| 河北省| 句容市|