CodeGym /コース /JAVA 25 SELF /EDT スレッドと UI の長時間処理

EDT スレッドと UI の長時間処理

JAVA 25 SELF
レベル 50 , レッスン 4
使用可能

1. EDT (Event Dispatch Thread) とは何か

Java のグラフィカルアプリケーションでは — Swing であれ JavaFX であれ — ユーザー操作(クリック、キー入力)やウィンドウの再描画は、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 のスレッドセーフ性に反するからです。コンポーネントの変更は 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 と長時間処理で起きがちなミス

エラー No.1: EDT で長時間処理を実行する。 アプリ全体が「フリーズ」し、ウィンドウが反応せず、ユーザーはプログラムが壊れたと思ってしまう。

エラー No.2: バックグラウンドスレッドから UI を更新しようとする。 UI のスレッドセーフ性に反し、バグや表示乱れ、クラッシュの原因になる。SwingUtilities.invokeLater または Platform.runLater を使う。

エラー No.3: バックグラウンドタスクでのエラー処理がない。 例外が「消えて」しまい、ユーザーは何が起きたか分からない。Swing では done() をオーバーライドして get() を読む。JavaFX では setOnFailed を購読する。

エラー No.4: 長時間処理をキャンセルできない。 ユーザーが読み込みや計算を中断できない。キャンセルをサポートする(SwingWorker.cancelTask.cancel)とともに、タスク内でキャンセルフラグを確認する。

エラー No.5: 進捗表示がない。 ユーザーはアプリが「フリーズ」したと思ってしまう。Swing では結果の公開とプログレスバーを SwingWorker と併用し、JavaFX では updateProgress と可視化されたインジケータを使う。

1
アンケート/クイズ
イベントとイベント処理、レベル 50、レッスン 4
使用不可
イベントとイベント処理
イベントとイベント処理
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION