當使用 Widget 工具箱時,務必瞭解用於讀取及分派平台 GUI 事件的基礎執行緒模型。 UI 執行緒的實作方式會影響在應用程式碼中使用 Java 執行緒時應用程式必須遵循 的規則。
在任何 GUI 應用程式下,無論是它的語言或 UI 工具箱,OS 平台會偵測 GUI 事件並放 置到應用程式事件佇列中。雖然不同 OS 平上的機制稍有不同,但基礎觀念都類似。 當使用者按一下滑鼠、鍵入字元或在視窗表面工作,OS 會產生應用程式 GUI 事件, 例如點選、按鍵或視窗繪製事件。它判定哪一個視窗和應用程式應接收每一個事件, 並將該事件放置到應用程式的事件佇列中。
任何一般視窗 GUI 應用程式的基礎結構為事件迴圈。應用程式起始設定,然後啟動 僅讀取佇列中的 GUI 事件並相應地做出回應的迴圈。任何工作必須在處理這其中一個事件 的同時快速完成,使 GUI 系統繼續對使用者作出回應。
UI 事件所觸發的長時間作業應在個別的執行緒中執行,以便事件迴圈執行緒快速傳回及 從應用程式佇列中提取下一個事件。不過,其他執行緒中 Widget 和平台 API 的存取 權必須以明確的鎖定和序列化控制。無法遵循規則的應用程式會導致 OS 呼叫失敗, 更糟的是會鎖定整個 GUI 系統。
使用 C 的原生 GUI 程式設計師完全熟悉使用平台事件迴圈的設計考量。 不過,Java 中的高階 Widget 工具箱通常藉由隱藏平台事件迴圈來避免應用程式開 發人員發生 UI 執行緒作業問題。
達到此目的之一般方式為設定一個從事件迴圈讀取及分派的 private 工具箱 UI 執行緒, 並將事件傳至由個別執行緒中執行的應用程式所維護的內部佇列。這使工具箱有足 夠的時間回應作業系統,而不對處理事件的應用程式計時加上任何限制。應用程式仍 須使用特殊鎖定技術,從它們的應用程式執行緒中存取 UI 程式碼,而這是在整個程 式碼中一致地完成,因為所有應用程式碼是在非 UI 執行緒中執行。
雖然聽起來像是「保護」應用程式免於發生 UI 執行緒作業問題,但實際 上還是會造成許多問題發生。
當 GUI 事件的計時與 Java 執行緒作業實作方式和應用程式效能有關時,除錯和診 斷問題就會變得困難。
新式的 GUI 平台透過事件佇列執行許多的最佳化。一般最佳化是將連續的繪製事件收 合在佇列中。每隔一段時間就必須重繪視窗的某個部份,才能檢查佇列是否有重疊或尚未 分派的多餘繪製事件。這些事件可合併成一個繪製事件,如此可降低應用程式繪製程 式碼的執行頻率及不穩定。如果 Widget 工具箱將佇列中的事件快速拉出再將它們遞回 內部佇列,則會使這個最佳化失效。
改變開發人員對執行緒作業模型的感覺,會使得具有以其他語言和工具箱設計原 生 GUI 系統經驗的程式程計師產生混淆。
SWT 遵循直接由平台支援的執行緒作業模型。應用程式在其主要執行緒中執行事件 迴圈並直接從這個執行緒分派事件。這是應用程式的「UI 執行緒」。
附註:就技術層面而言,UI 執行緒是建立 Display 的執行緒。事實 上,這也是執行事件迴圈和建立 Widget 的執行緒。
因為所有事件碼是從應用程式的 UI 執行緒觸發,所以處理事件的應用程式碼可任意 存取 Widget 及呼叫圖形,而不需要任何特殊技術。不過,應用程式在執行回應事件的 長時間作業時負責分出計算執行緒。
附註:SWT 會對必須從 UI 執行緒執行但卻從非 UI 執行緒執行的任何呼叫 觸發 SWTException。
SWT 應用程式的 main 執行緒(包括事件迴圈)有如下列所示:
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 (); }
一旦建立 Widget 並開啟 Shell 後,應用程式會從 OS 佇列讀取及分派事件,直到除去 Shell 視窗為止。如果佇列中沒有適用的事件,則會通知 display 暫停,讓其他 應用程式有機會執行。
附註:SWT 應用程式最常見的執行緒作業模型是執行單一 UI 執行緒,以及在 計算執行緒中執行長作業。不過,SWT 不針對這個模型來限制開發人員。 應用程式可執行每一個執行緒中,具有個別事件迴圈的多重 UI 執行緒。
SWT 提供從背景緒呼叫 Widget 和圖形的特殊存取方法。
想要從非 UI 執行緒呼叫 UI 程式碼的應用程式必須提供呼叫 UI 程式碼的 Runnable。Display 類別中的 syncExec(Runnable) 和 asyncExec(Runnable) 方法, 用於在適當時機時,在 UI 執行緒執行這些 Runnable。
下列程式碼片段示範使用這些方法的型樣:
// 執行耗時的計算 ... // 現在更新 UI。我們不依賴結果。 // 因此使用非同步。 Display.getDefault ().asyncExec (new Runnable () { public void run() { myWindow.redraw(); } }); // 現在做更多計算 ...
當您從頭開始實作 SWT 應用程式時,執行緒作業規則就非常明確,因為您控制事件 迴圈的建立和在應用程式中分出計算執行緒的決策。
如果您正要提供外掛程式碼給工作台,JFace 或工作台程式碼中沒有隱藏執行緒作業「魅力」。這些規則直接明確: