1. 程式人生 > >關於Eclipse中UI程式的執行緒的討論

關於Eclipse中UI程式的執行緒的討論

雖然各個作業系統之間的執行緒機制是不一樣的,但是大致是相同的。當用戶使用GUI程式時,如果點滑鼠或按下鍵盤上的鍵等時,作業系統會產 生對應的GUI事件,它來決定哪個視窗或程式來接受每一個事件並且放到程式的事件佇列中.

       任何GUI程式的底層結構就是一個事件迴圈.程式首先初始化事件迴圈,並開始迴圈,這個迴圈會從事件佇列依次接收GUI事件並一一做出相應的 反應.程式應該對事件做出快速的反應使程式一直對使用者有響應,舉個例子,使用者點了一下程式裡的一個按鈕結果程式就沒反應了,那麼這個程 序應該算是一個失敗的程式吧.

      如果某個UI事件引發了某個需要長時間的事務,那麼應該把它放到 一個另外的單獨的執行緒中,這樣程式的那個事件迴圈就能夠馬上回來響應使用者的下一個操作.執行緒是非常複雜的一個主題,如果處理的不好很容 易造成死鎖等很糟糕的情況.

      還好,eclipse為我們開發外掛提供了一個方便的UI執行緒包,大大的 簡化了很多底層複雜的東西.先看看幾個簡單的概念.

  1.SWT UI執行緒

  SWT用的是作業系統直接支援的執行緒模式,程式會 在主程式裡執行一個時間迴圈並依次在這個執行緒裡響應事件.看下面這段程式碼,UI執行緒就是建立Display的那個執行緒.

public static void main (String [] args) {
      Display display = new Display ();
      Shell shell = new Shell (display);
      shell.open ();
      // 開始事件迴圈
      // 關掉視窗後
      while (!shell.isDisposed ()) {
         if (! display.readAndDispatch ())
            display.sleep ();
      }
      display.dispose ();
   }


  簡單的小程式裡,一個UI執行緒就能夠滿足需要了。但如果是長時間的操作,你就最好不要用UI執行緒來做這些事,可以交給 Job去做.它其實就是另外啟動的執行緒,也就是等會我要說的非UI執行緒.

  2、Job

Job類由org.eclipse.core.runtime外掛提供.它能夠讓客戶程式設計師輕鬆的在另外的執行緒中執行程式碼。看一個小例子:

  Job job = new Job("My First Job") {
     protected IStatus run(IProgressMonitor monitor) {
           System.out.println("Hello World (from a background job)");
           return Status.OK_STATUS;
        }
     };
  job.setPriority (Job.SHORT);
  job.schedule(); // start as soon as possible

  Job的預設優先順序是Job.Long,這裡例子中的 優先順序要比它高,只要呼叫Job#schedule(),它就會盡快在另外的執行緒中執行run()中的程式碼。再看一個小例子:

final Job job = new Job("Long Running Job") {
        protected IStatus run (IProgressMonitor monitor) {
           try {
              while(hasMoreWorkToDo()) {
                 // do some work
                 // ...
              if (monitor.isCanceled()) return Status.CANCEL_STATUS;
             }
              return Status.OK_STATUS;
           } finally {
              schedule(60000); // start again in an hour
           }
        }
     };
  job.addJobChangeListener(new JobChangeAdapter() {
        public void done(IJobChangeEvent event) {
        if (event.getResult().isOK())
           postMessage("Job completed successfully");
           else
              postError("Job did not complete successfully");
        }
     });
  job.setSystem (true);
     job.schedule(); // start as soon as possible

  monitor是一個進度顯示條,它 會在執行job時自動顯示,如果任務成功執行完成,返回Status.OK_STATUS,如果中途被使用者在進度顯示條那裡中斷,就返回 Status.CANCEL_STATUS.上面schedule(60000);它是讓job每過1小時就自動執行,Job又一個非常強大的功能。然後後面是可以給job新增監聽器 ,
job.setSystem(true);這一句是把這個job設定為系統級別的.如果呼叫setUser(true),那麼就被定義為使用者級別的,使用者級別和預設級 別的job。

在執行時會以UI形式反映出來,如果是使用者job,那麼會彈出一個進度顯示視窗,能讓使用者選擇在後臺裡執行,下圖是一個job自動執行時的效 果:


 
  再介紹job常常用到的 一個方法Job#join(),系統呼叫到某個job,呼叫它的run()方法。再看下面這個例子:

    class TrivialJob extends Job {
      public TrivialJob() {
         super("Trivial Job");
      }
      public IStatus run(IProgressMonitor monitor) {
         System.out.println("This is a job");
         return Status.OK_STATUS;
      }
   }
  
  job的建立和計劃如下所示:

   TrivialJob job = new TrivialJob();
   System.out.println("About to schedule a job");
   job.schedule();
   System.out.println("Finished scheduling a job");
  
  他們的執行是 和時間沒關係的,輸出可能如下:

   About to schedule a job
   This is a job
   Finished scheduling a job

  也可能是:

   About to schedule a job
   Finished scheduling a job
   This is a job
   
如果希望某個job執行完成後在繼續時,可以使用join()方法,join()會一直阻塞到該job執行完。
  
  例 子:

   TrivialJob job = new TrivialJob();
   System.out.println("About to schedule a job");
   job.schedule();
   job.join();
   if (job.getResult().isOk())
      System.out.println("Job completed with success");
   else
      System.out.println("Job did not complete successfully");

  上面的程式碼執 行後,輸出應該就是這樣:

 About to schedule a job
   This is a job
   Job completed with success

  3、如果在Job中加上改變UI的程式碼就會失敗。原因如 下:

  如果是在非UI執行緒中呼叫UI,SWT就會丟擲一個SWTException,要在一個非UI執行緒改變UI的話有幾種技術:

  第一種 ,用:

Display#syncExec(Runnable)或
Diaplay#asyncExec(Runnable)

  第二種:

  已經開發了另外一種Job, 就是UIJob,可以直接在它裡面執行改變UI的程式碼,其實它就是在SWT的asyncExec()方法裡執行的.所有繼承UIJob的類應該覆寫runInUIThread 方法而不是run方法。

  在eclipse外掛和RCP開發中:

  使用者級別的 job是互操作性最強的,它不僅能夠讓使用者用Cancel鍵取消job,而且可以在Detail中展示具體情況,但是注意:

  Detail只會在下面 兩種方法中出現:

  IProgressService#busyCursorWhile或
  IProgressService#runInUI

  1) IProgressService#busyCursorWhile的用法例子:

  注意這裡的run()中做些和UI無關的事:

IProgressService progressService = PlatformUI.getWorkbench().getProgressService();
   progressService.busyCursorWhile(new IRunnableWithProgress() {
      public void run(IProgressMonitor monitor) {
         //do non-UI work
      }
   });

  效果:


2) IProgressService#runInUI的用法例子:

  注意這裡的run()中可以做些和UI有關的事。

progressService.runInUI(
      PlatformUI.getWorkbench ().getProgressService(),
      new IRunnableWithProgress() {
         public void run(IProgressMonitor monitor) {
            //do UI work
         }
      },
      Platform.getWorkspace().getRoot());

  效 果:



  這裡最後一個引數可以 是null,或者是這個操作的規則,在這裡我們是設定執行這個UI操作時鎖定工作臺.

  更加具體的可以參見:

http://help.eclipse.org/help30/index.jsp? topic=/org.eclipse.platform.doc.isv/guide/workbench_jobs.htm

  另外,有少數時候,我們不想彈出一個進度條視窗,而是 只在最底下的狀態列顯示就可以了,很簡單,寫自己的Job類時,在構造方法里加上一句:
setUser (false);就可以了.
(e129)