使用SwingWorker之一

本文示例代碼請從這兒下載
 
        正確理解和使用Swing線程模型編程是編寫響應靈活的Swing程序的關鍵。從Java SE 6開始引進的SwingWorker能幫你輕鬆的編寫多線程Swing程序,改善你Swing程序的結構,提高界面響應的靈活性。SDN(Sun developer Network)上有一篇很好的文章:Improve Application Performance With SwingWorker in Java SE 6詳細演示瞭如何使用SwingWorker改善Swing應用程序。把它翻譯過來同大家共享。
摘要
        桌面應用程序員常見的錯誤是誤用Swing事件調度線程(Event Dispatch Thread, EDT)。他們要麼從非UI線程訪問UI組件,要麼不考慮事件執行順序,要麼不使用獨立任務線程而在EDT線程上執行耗時任務,結果使編寫的應用程序變得響應遲鈍、速度很慢。耗時計算和輸入/輸出(IO)密集型任務不應放在Swing EDT上運行。發現這種問題的代碼並不容易,但Java SE 6提供了javax.swing.SwingWorker類,使修正這種代碼變得更容易。
        本文演示了一個使用SwingWorker類創建和管理任務線程的例子,描述瞭如何避免編寫運行緩慢、感覺遲鈍、容易失去響應的用戶界面。這個演示例子叫Image Search,它展示瞭如何使用SwingWorker API來和網站Flickr進行交互、搜索並下載圖像。
        如果需要理解Swing Ui的基本概念,包括事件處理和偵聽等UI編程,可以參照前面的文章,或從Sun官方網站下載閱讀Java教程的Swing部分
演示程序介紹
        Image Search執行的耗時任務是訪問Flickr網站服務,該任務不應該在EDT上執行。Image Search程序搜索Flickr站點,搜索匹配用戶輸入的查詢條件的圖像,下載匹配圖形的縮略圖。當用戶從縮略圖列表中選擇某縮略圖時,它將下載該圖的原始圖片。該演示程序使用SwingWorker類作爲任務線程,從而避免了在EDT上執行這些耗時任務。
        當用戶輸入查詢條件時,程序在Flickr網站請求一個圖像查詢。如果有符合查詢條件的圖像,程序下載上限爲100個的縮略圖像。可以修改程序改變下載圖像的數目。搜索和下載圖像的同時,有一進度條顯示搜索進度。圖1顯示了查詢字段和進度條:
使用SwingWorker之一
圖1,搜索圖像並顯示下載進度
        每當程序成功下載一個縮略圖片後,就添加到一個JList組件中,圖片從Flickr站點到達後就被添加列表中。程序使用SwingWorker的一個實例,程序能在每個圖片到達時添加到列表,而不用等待所有的圖片都到達。圖2顯示列表中的圖片:
使用SwingWorker之一
圖2匹配縮略圖的列表
        當從列表選擇一個圖片,程序將下載該圖片的原始圖片,並顯示在列表的下面。當大圖片下載時,另一進度條將顯示下載進度。圖3顯示列表和圖片下載進度條。
使用SwingWorker之一
圖3選中縮略圖下載大圖片
        最後,當所有圖片數據下載完畢後,程序在列表下方顯示圖片。
        程序使用SwingWorker來完成所有圖片搜索和下載任務。另外,程序還演示瞭如何取消任務,如何在任務完成之前獲得即時結果。該程序有兩個SwingWorker的子類:ImageSearcher和ImageRetriever。ImageSearcher類負責搜索和獲取圖片列表中的縮略圖,ImageRetriever類負責用戶從列表選擇時下載原始版本的圖片。本文用這個類來描述SwingWorker類的主要功能。圖4顯示程序的整個外觀。
使用SwingWorker之一
圖4使用SwingWorkere類創建響應靈活程序界面
回顧Swing線程基礎
        一個Swing程序中一般有下面三種類型的線程:
    * 初始化線程(Initial Thread)
    * UI事件調度線程(EDT)
    * 任務線程(Worker Thread)
        每個程序必須有一個main方法,這是程序的入口。該方法運行在初始化或啓動線程上。初始化線程讀取程序參數並初始化一些對象。在許多Swing程序中,該線程主要目的是啓動程序的圖形用戶界面(GUI)。一旦GUI啓動後,對於大多數事件驅動的桌面程序來說,初始化線程的工作就結束了。
        Swing程序只有一個用EDT,該線程負責GUI組件的繪製和更新,通過調用程序的事件處理器來響應用戶交互。所有事件處理都是在EDT上進行的,程序同UI組件和其基本數據模型的交互只允許在EDT上進行,所有運行在EDT上的任務應該儘快完成,以便UI能及時響應用戶輸入。
        Swing編程時應該注意以下兩點:
1.從其他線程訪問UI組件及其事件處理器會導致界面更新和繪製錯誤。
2.在EDT上執行耗時任務會使程序失去響應,這會使GUI事件阻塞在隊列中得不到處理。
3.應使用獨立的任務線程來執行耗時計算或輸入輸出密集型任務,比如同數據庫通信、訪問網站資源、讀寫大樹據量的文件。
        總之,任何干擾或延遲UI事件的處理只應該出現在獨立任務線程中;在初始化線程或任務線程同Swing組件或其缺省數據模型進行的交互都是非線程安全性操作。
        SwingWorker類幫你管理任務線程和Swing EDT之間的交互,儘管SwingWorker不能解決併發線程中遇到的所有問題,但的確有助於分離Swing EDT和任務線程,使它們各負其責:對於EDT來說,就是繪製和更新界面,並響應用戶輸入;對於任務線程來說,就是執行和界面無直接關係的耗時任務和I/O密集型操作。
使用合適線程
        初始化線程運行程序的main方法,該方法能處理許多任務。但在典型的Swing程序中,其主要任務就是創建和運行應用程序的界面。創建UI的點,也就是程序開始將控制權轉交給UI時的點,往往是同EDT交互出現問題的第一個地方。
        Image Search示例的主類是MainFrame,從其main方法啓動。許多程序使用下面方法啓動界面,但這是錯誤的啓動UI界面的方法:
public class MainFrame extends javax.swing.JFrame {
  ...
  public static void main(String[] args) {
    new MainFrame().setVisible(true);
  }
}

        儘管這種錯誤出現在開始,但仍然違反了不應在EDT外的其他線程同Swing組件交互的原則。這個錯誤尤其容易犯,線程同步問題雖然不是馬上顯示出來,但是還要注意避免這樣書寫。
        正確啓動UI界面應該如下:
public class MainFrame extends javax.swing.JFrame {
  ...
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        new MainFrame().setVisible(true);
      }
    });
  }
}

        使用NetBeans IDE的開發者應該對這段代碼很熟悉,NetBeans通常會自動生成這段代碼。這段啓動代碼雖然和SwingWorker沒有直接關係,但是這個編程範式很重要。SwingUtilities類包含一些靜態方法幫你同UI組件交互,其中invokeLater方法意思是在EDT上執行其Runnable任務。Runnable接口定義了可作爲獨立線程執行的任務。
        在初始化線程中使用invokeLater方法能正確的初始化程序界面。就像前面文章所提到的,此方法是異步執行的,也就是說調用會立即返回。創建界面後,大部分初始化線程基本上就結束了。
        通常有兩種辦法調用此方法:
    * SwingUtilities.invokeLater
    * EventQueue.invokeLater
        兩個方法都是正確的,選擇任何一個都可以。實際上,SwingUtilities版只是一個薄薄的封裝方法,它直接轉而調用EventQueue.invokeLater。因爲Swing框架本身經常調用SwingUtilities,使用SwingUtilities可以減少程序引入的類。
        另種將任務放到EDT執行的方法是SwingUtilities.invokeAndWait,不像invokeLater,invokeAndWait方法是阻塞執行的,它在EDT上執行Runnnable任務,直到任務執行完了,該方法才返回調用線程。
        invokeLater和invokeAndWait都在事件派發隊列中的所有事件都處理完之後才執行它們的Runnable任務,也就是說,這兩個方法將Runnable任務放在事件隊列的末尾。
        注意:雖然可以在其他線程上調用invokeLater,也可以在EDT上調用invokeLater,但是千萬不要在EDT線程上調用invokeAndWait方法!很容易理解,這樣做會造成線程競爭,程序就會陷入死鎖。
將EDT線程僅用於GUI任務
        Swing框架負責管理組件繪製、更新以及EDT上的線程處理。可以想象,該線程的事件隊列很繁忙,幾乎每一次GUI交互和事件都是通過它完成。事件隊列的上任務必須非常快,否則就會阻塞其他任務的執行,使隊列裏阻塞了很多等待執行的事件,造成界面響應不靈活,讓用戶感覺到界面響應速度很慢,使他們失去興趣。理想情況下,任何需時超過30到100毫秒的任務不應放在EDT上執行,否則用戶就會覺察到輸入和界面響應之間的延遲。
        幸運的是,不會僅僅因爲有複雜的任務、計算或輸入輸出密集任務需要作爲GUI事件處理任務執行,Swing的性能就要有所降低。畢竟有許多桌面程序執行耗時任務,比如處理電子表格公式、跨越網絡查詢數據庫、通過Internet向其他程序發送信息。即使有這些任務,界面仍然可以讓用戶感覺到響應靈活、快捷。編寫響應靈活的程序需要創建和管理獨立於EDT的線程。
        在Image Search程序中,有兩個事件如果完全在EDT上處理就會降低界面的響應速度:圖像搜索處理和選中圖片下載處理。
        兩個事件處理都要訪問Web服務,這些服務通常要許多秒後才能響應,在此期間,如果程序在EDT上進行Web服務交互,用戶就不能取消搜索或者同界面交互,像這兩種都不應該在EDT上運行。
        圖5顯示了在A和B點之間,EDT不能處理UI事件,AB兩點之間代表了程序訪問Flickr網站Web服務的IO操作時間:
使用SwingWorker之一
圖5. 在執行Web服務期間EDT不能響應UI事件
        javax.swing.SwingWorker類是Java SE 6中新出現的類,使用SwingWorker,程序能啓動一個任務線程來異步查詢,並馬上返回EDT線程。圖6顯示了使用SwingWorker後,事件處理立即返回,允許EDT繼續執行後續的UI事件。
使用SwingWorker之一
圖6.使用任務線程,程序能夠在避免在EDT上執行I/O密集型任務
(待續)
發佈了4 篇原創文章 · 獲贊 3 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章