程式 雖然各個作業系統之間的線程機制是不一樣的,但是大致是相同的。當使用者使用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
Job的功能是很強大的,還有很多功能我以後會介紹,也可以查閱官方協助文檔,這裡先把幾個常用的問題解決掉。參見:
http://help.eclipse.org/help30/index.jsp?topic=/org.eclipse.platform.doc.isv/guide/runtime_jobs.htm
3、如果在Job中加上改變UI的代碼就會失敗。原因如下:
如果是在非UI線程中調用UI,SWT就會拋出一個SWTException,要在一個非UI線程改變UI的話有幾種技術:
第一種,用:
Display#syncExec(Runnable)或
Diaplay#asyncExec(Runnable)
第二種:
已經開發了另外一種Job,就是UIJob,可以直接在它裡面運行改變UI的代碼,其實它就是在SWT的asyncExec()方法裡啟動並執行.所有繼承UIJob的類應該覆寫runInUIThread方法而不是run方法。
3.關於進度顯示
在Jface中:
org.eclipse.jface.operations包定義了一些介面用來在進度條下運行長時間的任務。可以參見:
http://help.eclipse.org/help30/index.jsp?topic=/org.eclipse.platform.doc.isv/guide/jface_operations.htm
在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);就可以了.