본문 바로가기
Eclipse RCP

백그라운드 작업과 진행 상황 보고 기능을 제공하는 Jobs API

by jayden-lee 2019. 4. 30.
728x90

Eclipse Framework Single UI Thread

Eclipse RCP 또는 Plugin은 사용자 인터페이스를 하나의 스레드로 처리한다. 이 스레드를 메인 스레드, UI 스레드라고 부른다. 해당 스레드에서 오래 걸리는 작업(네트워크, 파일 처리, 데이터베이스)을 수행하면, 사용자 인터페이스와 관련된 다른 처리가 차단 되는 현상이 발생하므로 주의해야 한다.

 

이 현상은 사용자가 느끼기에는 답답하고 애플리케이션 멈춘것처럼 보이기 때문에 피해야 하는 안티패턴이다. 따라서 오래 걸리는 작업은 백그라운드 스레드로 실행해서 UI가 멈추지 않도록 해야 한다.

 

Java 라이브러리에 있는 Thread, Timer를 이용해서 백그라운드 작업을 수행할 수 있지만, Eclipse Framework에서는 Jobs API를 제공한다. Jobs API는 작업 실행, 진행 상황 보고, 작업 그룹화 등 기능을 제공한다. 이클립스 작업 메커니즘은 UI 없이 작업을 수행하는 Job과 UI 스레드 환경에서 작업을 수행하는 UIJob이 있다.

Eclipse Framework Job API Document

백그라운드 작업 수행 예제

SampleHandler 클래스에 execute() 메서드 내부에 백그라운드로 작업을 수행하는 Job을 추가해보자. 커맨드에 SampleHandler를 연결해서 실행하면 에러가 발생한다. 에러가 발생한 이유는 MessageDialog(메시지 박스를 표시하는 UI 처리)를 백그라운드에서 수행했기 때문이다. UI 관련 처리는 오직 UI 스레드에서만 진행할 수 있기 때문이다.

public class SampleHandler extends AbstractHandler {

    @Override
    public Object execute(ExecutionEvent event) throws ExecutionException {
        // 백그라운드 작업을 수행하는 Job 생성한다.
        Job job = new Job("About to say Hello") {
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                try {
                    Thread.sleep(3000);
                } catch (Exception ex) {
                    System.err.println(ex.getMessage());
                }

                // 사용자에게 메시지 박스를 표시한다.
                // 에러 발생 하는 곳, UI 스레드에서 수행되어야 한다.
                MessageDialog.openInformation(null, "Title", "Content");

                return Status.OK_STATUS;
            }
        };

        // Job 수행을 시작한다.
        job.schedule();

        return null;
    }

}

 

UI Thread 오류

 

MessageDialog는 UI 스레드에서 처리할 수 있도록 다음 코드처럼 변경해야 한다. Display 클래스의 asyncExec() 메서드를 이용하면 UI 스레드에서 작업을 수행할 수 있다. asyncExec()는 Swing의 SwingUtilities.invokeLater() 메서드와 유사하다.

Display 클래스 설명

Display.getDefault().asyncExec(new Runnable() {
    @Override
    public void run() {
        // 사용자에게 메시지 박스를 표시한다.
        MessageDialog.openInformation(null, "Title", "Content");
    }
});

진행 상황(Progress Bar) 모니터링 예제

위에서 Job을 이용해서 백그라운드 작업을 어떻게 수행하는지에 대해서 살펴봤다. 이번에는 작업이 수행되는 중간에 사용자에게 작업의 진행 상태를 보여주는 방법에 대해 알아보자.

 

Job은 기본적으로 진행 상태에 대한 상세한 정보를 제공하지 않고 작업 중(busy)이라는 메시지 표시를 제공한다. 작업의 진행 상황을 사용자에게 알려주고 작업을 취소하는 방법을 제공하려면, Job에서 진행 상황 모니터(IProgressMonitor)를 이용해야 한다.

 

IProgressMonitor를 이용해서 작업의 상태와 전체 작업량을 설정한다. 프로그램을 실행하고 SampleHandler와 연결된 커맨드를 실행하면 진행 뷰(Progress View)에 진행 상황을 표시한다.

public class SampleHandler extends AbstractHandler {

    @Override
    public Object execute(ExecutionEvent event) throws ExecutionException {
        // 백그라운드 작업을 수행하는 Job 생성한다.
        Job job = new Job("About to say Hello") {
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                try {
                    monitor.beginTask("작업 중입니다....", 10000);

                    for (int i = 0; i < 1000; i++) {
                        Thread.sleep(1000);
                        monitor.worked(10);
                    }
                } catch (Exception ex) {
                    System.err.println(ex.getMessage());
                } finally {
                    monitor.done();
                }

                Display.getDefault().asyncExec(new Runnable() {
                    @Override
                    public void run() {
                        MessageDialog.openInformation(null, "데이터베이스 작업", "작업이 완료되었습니다.");
                    }
                });

                return Status.OK_STATUS;
            }
        };

        // Job 수행을 시작한다.
        job.schedule();

        return null;
    }

}

 

작업 진행 상태를 표시하는 Progress View

 

Eclipse에서 Progress 뷰 열기

 

작업의 진행을 보여주는 것은 사용자에게 어떤 작업이 일어나고 있다는 것을 알려줄 수 있기 때문에 좋은 기능이다. 만약 사용자가 다른 급한일이 생기면 현재 진행 되고 있는 작업을 취소하고 싶을 수 있다. IProgressMonitor는 사용자가 작업을 취소하면 isCanceled() 메서드에서 true를 반환한다. 작업을 진행하는 로직을 수행하는 곳에 isCanceled() 메서드를 추가하면, 작업 취소 요청이 왔을 경우에 작업을 취소 할 수 있다.

 

isCanceled() 메서드는 단일 필드에만 접근하여 구현하기 때문에 자주 호출해도 성능에 나쁜 영향을 끼치지 않는다. 그리고 필요한만큼 호출하지 않으면, 사용자는 자신의 취소 요청이 바로 수행되지 않는 것을 체감상 느낄 수 있다. 그러므로 사용자가 인지하지 못하도록 필요한만큼 충분히 호출해야 한다.

protected IStatus run(IProgressMonitor monitor) {
    try {
        monitor.beginTask("작업 중입니다....", 10000);

        for (int i = 0; i < 1000 && monitor.isCanceled(); i++) {
            Thread.sleep(1000);
            monitor.worked(10);
         }
    } catch (Exception ex) {
        System.err.println(ex.getMessage());
    } finally {
        monitor.done();
    }

    if (monitor.isCanceled()) {
        Display.getDefault().asyncExec(new Runnable() {
        @Override
        public void run() {
            MessageDialog.openInformation(null, "데이터베이스 작업", "작업이 완료되었습니다.");
        }
        });
    }

    return Status.OK_STATUS;
}

댓글