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 컴포넌트는 스레드 세이프하지 않습니다. 레이스 컨디션과 화면 깨짐을 피하려면, 모든 UI 변경을 하나의 장소 — 즉 EDT에서만 — 수행해야 합니다. 한 명의 계산원이 있는 계산대와 같습니다. 같은 영수증을 여러 사람이 동시에 처리할 수는 없습니다.

Swing에서는 EDT가 이벤트 핸들러와 컴포넌트 리페인트(예: actionPerformed)를 실행합니다. JavaFX의 대응 스레드는 JavaFX Application Thread로, 여기서 UI 업데이트와 setOnAction 같은 핸들러가 실행됩니다.

2. UI에서의 ‘오래 걸리는 작업’ 문제

EDT에서 오래 걸리는 작업을 실행하면 무엇이 일어날까요?

사용자가 버튼을 누르면 핸들러(예: actionPerformed 또는 setOnAction)가 EDT에서 실행됩니다. 그 내부에서 큰 파일 읽기, 네트워크 요청, 복잡한 계산 같은 무거운 작업을 실행하면, 전체 UI가 ‘멈춥니다’:

  • 창이 클릭과 키 입력에 반응하지 않습니다.
  • 리페인트가 멈춰서 창을 움직이면 화면이 ‘끌리는’ 것처럼 보입니다.
  • 사용자는 프로그램이 ‘고장 났다’고 생각합니다.

잘못된 코드 예시(Swing):

button.addActionListener(e -> {
    // EDT에서 바로 오래 걸리는 작업!
    longOperation(); // 예: 큰 파일 읽기
    label.setText("완료!");
});

결과: longOperation()이 실행되는 동안 창은 사용자 입력에 반응하지 않습니다.

왜 그럴까요? EDT는 작업을 큐에 넣어 순서대로 처리하며, 한 번에 하나만 실행할 수 있습니다. 무거운 작업으로 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 변경은 안전한 메서드(updateMessage, updateProgress)나 핸들러(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 스레드 세이프티를 위반하면 버그, 화면 깨짐, 크래시로 이어질 수 있습니다. SwingUtilities.invokeLater 또는 Platform.runLater를 사용하세요.

오류 3: 백그라운드 작업에서 오류 처리를 하지 않음. 예외가 ‘사라져서’ 사용자는 무엇이 잘못됐는지 알 수 없습니다. Swing에서는 done()을 오버라이드하고 get()을 읽으세요. JavaFX에서는 setOnFailed에 구독하세요.

오류 4: 오래 걸리는 작업을 취소할 수 없음. 사용자는 로드/계산을 중단할 수 없습니다. 취소 지원(SwingWorker.cancel, Task.cancel)을 사용하고 작업 내부에서 취소 플래그를 확인하세요.

오류 5: 진행률 표시가 없음. 사용자는 프로그램이 ‘멈췄다’고 느낍니다. Swing에서는 SwingWorker의 결과 게시와 ProgressBar를 함께 사용하고, JavaFX에서는 updateProgress와 시각적 인디케이터를 사용하세요.

1
설문조사/퀴즈
이벤트와 이벤트 처리, 레벨 50, 레슨 4
사용 불가능
이벤트와 이벤트 처리
이벤트와 이벤트 처리
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION