本地事件調(diào)度
對于任何的GUI應(yīng)用程序,不管所使用的是哪一種編程語言和UI工具包,背后的運行機制都是操作系統(tǒng)探測GUI事件并把它們放到應(yīng)用程序的事件隊列中去。這種機制在不同的操作系統(tǒng)平臺中大同小異。當用戶點擊鼠標、鍵入字符、或者使窗口獲得焦點,操作系統(tǒng)就會生成應(yīng)用程序的GUI事件,例如鼠標點擊、鍵盤輸入、或窗口重繪事件。操作系統(tǒng)決定哪一個窗口和應(yīng)用程序應(yīng)該接收用戶觸發(fā)的每一個事件并把它放入應(yīng)用程序的事件隊列中。
任何基于窗口的GUI應(yīng)用程序的底層實現(xiàn)結(jié)構(gòu)都是一個事件循環(huán)(event loop),應(yīng)用程序初始化并運行一個循環(huán)(loop),用于從事件隊列中讀取GUI事件,并作出相應(yīng)的反應(yīng)。處理事件的工作必須迅速完成,以保證GUI應(yīng)用程序能夠?qū)τ脩糇鞒隹焖俜磻?yīng)。
UI事件觸發(fā)的耗時較長的操作應(yīng)該在一個單獨的線程中執(zhí)行,這樣才能讓事件循環(huán)主線程能夠快速返回,獲取應(yīng)用程序事件隊列中的下一個事件。但是,在非UI線程中訪問圖形界面部件和平臺API 必須通過鎖和串行化的機制(locking and serialization)來實現(xiàn)。違反這個規(guī)則的應(yīng)用程序會引起系統(tǒng)調(diào)用失敗,更嚴重的是鎖住整個GUI系統(tǒng),使GUI失去反應(yīng)。
SWT UI 線程
SWT 遵循系統(tǒng)平臺所直接支持的這種線程模型,應(yīng)用程序在它的主線程中運行事件循環(huán)(event loop),并在主線程中直接調(diào)度線程。UI線程就是Display 對象被創(chuàng)建的線程,所有其他的圖形部件都必須在這個UI線程中創(chuàng)建。
既然所有的處理事件的代碼是在應(yīng)用程序的UI線程中觸發(fā)的,那么處理事件的程序代碼就能夠不需要任何特殊方法自由訪問和調(diào)用圖形部件。不過,在處理長耗時的事件操作時,需要使用多線程來實現(xiàn)應(yīng)用程序的功能。
注:在非UI線程中調(diào)用任何必須在UI線程調(diào)用的程序,SWT將會觸發(fā)一個 SWTException 異常。
SWT 應(yīng)用程序的主線程,包括事件循環(huán),其代碼結(jié)構(gòu)如下:
- public static void main (String [] args) {
- Display display = new Display ();
- Shell shell = new Shell (display);
- shell.open ();
- // start the event loop. We stop when the user has done
- // something to dispose our window.
- while (!shell.isDisposed ()) {
- if (!display.readAndDispatch ())
- display.sleep ();
- }
- display.dispose ();
- }
創(chuàng)建圖形部件和打開shell 窗口之后,程序讀取和分發(fā)來自操作系統(tǒng)事件隊列的事件,直到shell 窗口被銷毀。如果在隊列中不存在有效事件,display 進入睡眠狀態(tài),把運行機會交給其他程序。
SWT 提供了在后臺主線程中調(diào)用圖形部件代碼的訪問方法。
運行非UI線程
在非UI 線程中不能直接調(diào)用UI 代碼,必須提供一個 Runnable對象,在Runable中調(diào)用UI代碼。Display 類中的syncExec(Runnable) 和 asyncExec(Runnable) 方法用于在事件循環(huán)運行期間,在UI 線程中運行這些Runnable對象。
- syncExec(Runnable) 當非UI 線程中的程序代碼依賴于UI 代碼的返回值,或者為了確保在返回到主線程之前Runnable 必須執(zhí)行完成時,應(yīng)該使用這個方法。SWT 將會阻塞調(diào)用線程,直到在應(yīng)用程序的UI 線程中運行的這個Runnable運行結(jié)束為止。例如,一個后臺線程需要基于一個窗口的當前尺寸進行某種計算,就會需要同步地運行獲取窗口尺寸的代碼,然后繼續(xù)其后面的計算。
- asyncExec(Runnable) 當程序需要執(zhí)行一些UI 操作,但在繼續(xù)執(zhí)行之前不依賴這些操作必須完成的時候,應(yīng)該使用這個方法。例如,后臺主線程更新進度條,或者重繪一個窗口,它可以異步地發(fā)出更新或重繪的請求,并接著繼續(xù)后面的處理,在這種情況下,后臺主線線程的運行時間和Runnable的運行沒有必然的關(guān)系。
- // do time-intensive computations
- ...
- // now update the UI. We don't depend on the result,
- // so use async.
- display.asyncExec (new Runnable () {
- public void run () {
- if (!myWindow.isDisposed())
- myWindow.redraw ();
- }
- });
- // now do more computations
- ...
工作臺(Workbench)和多線程
實現(xiàn)SWT應(yīng)用程序的多線程規(guī)則非常明確,你可以控制事件循環(huán)的初始化,在應(yīng)用程序中使用多線程解決復(fù)雜問題。
向工作臺添加插件時的工作機制要父子一些,下面是使用平臺(workbench platform)UI 類的一些“規(guī)約”(Rules of engagement),隨著eclipse 的不斷發(fā)布,可能會出現(xiàn)一些例外:
- 通常,任何添加到平臺中的工作臺(workbench) UI 擴展都是在工作臺的UI 主線程中執(zhí)行的,除非是明確地把它們添加線程中或者是后臺作業(yè)(background job)中,例如后臺作業(yè)進度條。
- 如果從工作臺接收到一個事件,不能保證它是在UI線程中執(zhí)行的,查閱定義了監(jiān)聽器或事件的類的java文檔,如果沒有特別說明使用線程,這個類就是一個UI 相關(guān)類,可以在工作臺主線程中獲得和運行。
- 同樣,除非是文檔明確說明,平臺UI庫不能視作是線程安全的。請注意,大部分平臺UI類是在觸發(fā)事件的調(diào)用線程中運行監(jiān)聽器的,平臺和JFace API調(diào)用并不檢查是在UI 線程中執(zhí)行的,這意味著如果在非UI 線程中調(diào)用一個能夠觸發(fā)事件的方法,可能會引入問題。從非UI 線程中調(diào)用SWT的API,SWT會拋出 SWTException 異常。通常,除非文檔中明確規(guī)定,避免在別的線程中調(diào)用平臺UI 代碼。
- 如果你的插件使用多線程或工作臺作業(yè)(workbench job),必須使用 Display 類的asyncExec(Runnable) 或 syncExec(Runnable) 方法,類調(diào)用任何的工作臺(workbench)、JFace或SWT 的應(yīng)用程序接口(API),除非是API明確說明是可以直接調(diào)用的。
- 如果在插件中使用 JFace的IRunnableContext 接口 調(diào)用進度監(jiān)視器(progress monitor),以運行一個操作,IRunnableContext 提供了一個參數(shù)來確定是不是在一個新的線程中運行操作。