CodeGym /課程 /JAVA 25 SELF /EDT 執行緒與 UI 中的長時間作業

EDT 執行緒與 UI 中的長時間作業

JAVA 25 SELF
等級 50 , 課堂 4
開放

1. 什麼是 EDT(Event Dispatch Thread)

在 Java 的圖形應用程式中 — 不論是 SwingJavaFX — 使用者的所有操作(點擊、按鍵)以及視窗重繪都在名為 EDT 的特殊執行緒中處理(Event Dispatch Thread,事件派發執行緒)。

為什麼需要它? Java 中的 UI 元件不是執行緒安全的。為了避免競態條件與畫面偽影,所有介面變更都必須嚴格在同一個地方執行 — 在 EDT。就像只有一位收銀員的櫃檯:同一張發票不可能同時由多個人處理。

Swing 中,EDT 會執行事件處理器與元件重繪(例如 actionPerformed)。在 JavaFX 中的對應是 JavaFX Application Thread,UI 更新與像 setOnAction 之類的處理器都在其上執行。

2. UI 中「長時間作業」的問題

如果在 EDT 中執行長時間作業會怎樣?

當使用者按下按鈕時,事件處理器(例如 actionPerformedsetOnAction)會在 EDT 上執行。如果其中啟動了繁重任務(讀取大型檔案、網路請求、複雜計算),整個 UI 會「卡住」

  • 視窗不再回應滑鼠點擊與鍵盤輸入。
  • 重繪停止 — 移動時視窗會「黏住」。
  • 使用者會認為程式「壞掉了」。

錯誤範例(Swing):

button.addActionListener(e -> {
    // 直接在 EDT 內執行耗時操作!
    longOperation(); // 例如讀取大型檔案
    label.setText("完成!");
});

結果:在執行 longOperation() 的期間,視窗不會回應使用者。

為什麼?EDT 會按順序處理任務,而且一次只能做一件事。當它被你的長時間作業占用時,就無法處理點擊或重繪。

3. 解法:長時間作業 — 一律放到背景執行緒

原則:

  • 所有耗時操作 — 一律在背景執行緒。
  • 所有 UI 更新 — 一律在 EDT/JavaFX Application Thread

在獨立執行緒啟動長時間作業

範例(Swing):

button.addActionListener(e -> {
    new Thread(() -> {
        longOperation(); // 在背景執行緒中執行
        // 接著要更新 UI——只能從 EDT!
        SwingUtilities.invokeLater(() -> label.setText("完成!"));
    }).start();
});

範例(JavaFX):

button.setOnAction(e -> {
    new Thread(() -> {
        longOperation();
        // 透過 Platform.runLater 更新 UI
        Platform.runLater(() -> label.setText("完成!"));
    }).start();
});

如何從背景執行緒更新 UI?

  • Swing:使用 SwingUtilities.invokeLater(Runnable) — 任務會被放入 EDT 佇列。
  • JavaFX:使用 Platform.runLater(Runnable) — 任務會在 JavaFX Application Thread 上執行。

為什麼不能直接呼叫 label.setText(...) 自背景執行緒?因為這會違反 UI 的執行緒安全:元件只能由介面執行緒變更。

用於背景工作的專用類別

在實際應用中,經常需要顯示進度、允許取消,並處理錯誤。為此可以使用:

  • SwingWorker<T, V> — 用於 Swing;
  • Task<V>Service<V> — 用於 JavaFX。

範例(JavaFX Task):

Task<Void> task = new Task<>() {
    @Override
    protected Void call() throws Exception {
        longOperation();
        // 可以更新進度:updateProgress(...)
        return null;
    }
};

task.setOnSucceeded(e -> label.setText("完成!"));
task.setOnFailed(e -> label.setText("錯誤!"));

new Thread(task).start();

優點:支援進度、取消、成功/錯誤事件。UI 變更可透過安全方法(updateMessageupdateProgress)或處理器(setOnSucceeded 等)。

4. 正確與錯誤的模式

錯誤做法:在事件處理器中執行長時間作業

button.setOnAction(e -> longOperation()); // UI 會卡住!

正確做法:在獨立執行緒中執行長時間作業

button.setOnAction(e -> new Thread(() -> longOperation()).start());

更好:使用 Task/Worker

JavaFX:

button.setOnAction(e -> {
    Task<Void> task = new Task<>() {
        @Override
        protected Void call() throws Exception {
            longOperation();
            return null;
        }
    };
    task.setOnSucceeded(ev -> label.setText("完成!"));
    new Thread(task).start();
});

Swing:

button.addActionListener(e -> {
    SwingWorker<Void, Void> worker = new SwingWorker<>() {
        @Override
        protected Void doInBackground() throws Exception {
            longOperation();
            return null;
        }
        @Override
        protected void done() {
            label.setText("完成!");
        }
    };
    worker.execute();
});

5. 實作:檔案載入範例

JavaFX:

button.setOnAction(e -> {
    Task<String> task = new Task<>() {
        @Override
        protected String call() throws Exception {
            // 模擬長時間載入
            Thread.sleep(2000);
            return "檔案已載入!";
        }
    };
    task.setOnSucceeded(ev -> label.setText(task.getValue()));
    new Thread(task).start();
});

Swing:

button.addActionListener(e -> {
    SwingWorker<String, Void> worker = new SwingWorker<>() {
        @Override
        protected String doInBackground() throws Exception {
            Thread.sleep(2000);
            return "檔案已載入!";
        }
        @Override
        protected void done() {
            try {
                label.setText(get());
            } catch (Exception ex) {
                label.setText("錯誤!");
            }
        }
    };
    worker.execute();
});

6. 使用 EDT 與長時間作業時的常見錯誤

錯誤 1:在 EDT 執行長時間作業。整個應用程式會「卡住」,視窗沒有回應,使用者會以為程式壞了。

錯誤 2:嘗試從背景執行緒更新 UI。違反 UI 的執行緒安全可能導致 bug、畫面偽影與崩潰。請使用 SwingUtilities.invokeLaterPlatform.runLater

錯誤 3:背景工作未處理錯誤。例外會被遺失,使用者不知道出了什麼問題。在 Swing 中 — 覆寫 done() 並呼叫 get();在 JavaFX 中 — 監聽 setOnFailed

錯誤 4:無法取消長時間作業。使用者無法中止載入/計算。請使用取消支援(SwingWorker.cancelTask.cancel),並在任務內檢查取消旗標。

錯誤 5:沒有進度指示。使用者會以為程式卡住。在 Swing 中 — 使用結果發布與進度列搭配 SwingWorker;在 JavaFX 中 — 使用 updateProgress 與視覺化指示器。

1
問卷/小測驗
事件與事件處理,等級 50,課堂 4
未開放
事件與事件處理
事件與事件處理
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION