메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

예제로 설명하는 쓰레드 제어하기(2)

한빛미디어

|

2007-05-11

|

by HANBIT

13,211

제공 : 한빛 네트워크
저자 : Viraj Shetty
역자 : 조춘식
원문 : Controlling Threads by Example

쓰레드 진행 사항 결정하기

이번 iteration에서는 우리는 검색 상황을 보여주는 기능을 추가한다. 이 인터페이스는 별로 달라진 건 없다. (그림 6 참고)


[그림 6] 작업 모니터링을 위한 사용자 인터페이스

Search 버튼이 클릭되면, Cancel 버튼이 enable 될 것이다. 또한 검색 결과는 마지막이 아니라 검색되는 즉시 화면에 표시되고, 상태 필드는 진행상황을 표시하게 된다. 이런 기능을 추가 구현하기 위해 우리는 FileFinder 코드를 리팩토링하여 검색 쓰레드가 작업 진행 퍼센트를 알 수 있도록 해야 한다. 현재 구현된 FileFinder는 진행상황 계산 기능이 없다. 그러니, 두개의 함수를 가지는 TokenSearchWork라는 클래스를 만들어보자.
  • getAllDirectories(): 검색이 수행될 디렉토리들을 결정한다. 실행 작업 범위를 검색 쓰레드에 알려준다. 이로서 검색작업을 작은 몇 개의 작업으로 효과적으로 나눌 수 있다.
  • findFilesInDirectory(): 특정 디렉토리 검색을 수행한다.
여기 TokenSearchWork 클래스의 코드 일부이다.
public class TokenSearchWork {

    private File rootDir;

    public TokenSearchWork(File rootDir) {
        this.rootDir = rootDir;
    }

    public List getAllDirectories() 
        throws InterruptedException {
        
        return findDirs(rootDir);
    }

    private List findDirs(File directory) 
        throws InterruptedException {

        checkForInterrupt();

        ....

        return foundDirs;
    }

    ....

    private void checkForInterrupt() 
        throws InterruptedException {
        
        if (Thread.currentThread()
                        .isInterrupted()) {
            throw new InterruptedException(
                "Interrupted !!!");
        }
    }

}
InterruptedException을 눈 여겨 보자. 두 함수 모두에서 이 예외를 던질 수 있도록 선언되었다. 이것은 검색 쓰레드를 멈추는데 사용된다. 또한 인터럽트 발생을 확인하고 InterruptedException을 생성해 던지는 간단한 private 함수 checkForInterrup()를 만들것이다. 이 함수는 코드를 좀더 간결하게 모으는 용도이다. 이 예외는 검색 쓰레드에 의해 적절히 처리될 것이다. 이제 SearchThread의 run 함수는 새로 만든 TokenSearchWork 클래스를 사용하도록 변경한다.
public void run() {

    List allFiles = new ArrayList();
    TokenSearchWork work 
        = new TokenSearchWork(rootDir);
    int percentDone = 0;
    try {
        List allDirs 
                = work.getAllDirectories();
        int sizeWork = allDirs.size();
        for (int j = 0; 
                  j < allDirs.size(); j++) {

            File directory 
                = (File) allDirs.get(j);
            allFiles.addAll(
                work.findFilesInDirectory(
                        directory, token));

            percentDone 
                = 100 * (j + 1) / sizeWork;
            form.setTextArea(allFiles, 
                percentDone, false);
        }
    } catch (InterruptedException intExp) {
        // The Task was interrupted
    }

    form.setTextArea(allFiles, 
        percentDone, true);

}
run() 함수는 먼저 TokenSearchWork 클래를 생성한다. 그리고 getAllDirectories() 함수를 호출하여 전체 디렉토리의 리스트를 얻는다. 이것은 수행되어야 할 총 작업량을 쓰레드에게 알려준다. 그 다음 디렉토리 전부를 검색 쓰레드가 findFilesInDirectory(..) 호출을 통해 파일에 문자열을 검색한다. 이로서, 검색 쓰레드는 항상 완료된 진행상황을 알 수 있다. 또한 InterruptedException은 for(..) 루프를 빠져나올수 있도록 한다. 완료된 퍼센티지와 취소를 돕기 위해 SearchForm.setTextArea()의 인자가 바뀌었음을 주의해라. 두 개의 새로운 인자가 도입되었다: percentDone 과 작업 완료 여부 확인을 위한 done이다. SearchForm은 아래와 같이 이 변경을 처리한다.
public void setTextArea(List javaFiles, 
        int percentDone, boolean done) {

    ....

    SwingUtilities.invokeLater(
        new SetAreaRunner(area, areaBuffer
          .toString(), percentDone, done));
}

private class SetAreaRunner 
        implements Runnable {
    
    private JTextArea area;
    private String text;
    private int percent;
    private boolean done;

    public SetAreaRunner(JTextArea area, 
        String text, int percent, 
        boolean done) {
        
        this.area = area;
        this.text = text;
        this.percent = percent;
        this.done = done;
    }

    public void run() {
       // Set the UI fields correctly
    }
}
이번 iteration에서의 기능은 대부분 시나리오에서 충분할 것이다. 그러나 pause()와 resume() 같은 기능이 필요한 순간이 있을 수 있다. stop() 을 제공했던 것처럼, 자바는 Thread 클래스에서 이 함수들을 제공한다. 그러나 사용상 위험 때문에 이 함수들은 deprecated 되었다. 하지만 우린 이 기능이 필요하므로 스스로 pause()와 resume()을 만들어 보도록 하겠다.

쓰레드 일시중지 하기 그리고 재실행하기

이번 iteration에서는 Pause와 Resume 기능을 구현한다. 두 개의 새 버튼이 추가되었다. Pause 버튼이 클릭된 화면은 [그림 7]에서 볼 수 있다.


[그림 7] Pause/Resume을 위한 사용자 인터페이스

Pause와 Resume 기능을 구현하기 위하여, Pause 버튼이 눌린 경우 어떤 방법으로든 쓰레드를 잠든 상태로 만들고 Resume 버튼이 눌리면 다시 해당 쓰레드를 깨어나도록 해야 한다. 자바 안에서 최고의 방법은 wait(..)와 notify(…)함수를 사용하는 것이다. 우리는 SearchThread안에 새로 request변수를 도입한다. 이 변수는 다음 3가지 값들 중에 하나의 값을 가진다.
private static final int NORMAL   = 0;
private static final int PAUSE    = 1;
private static final int RESUME   = 2;
Pause와 Resume을 위한 새로운 두 개의 함수는 아래와 같이 SearchTread에 추가하자. (synchronized 키워드를 주의하라.):
public synchronized void pauseWork() {
    request = PAUSE;
    notify();
}

public synchronized void resumeWork() {
    if (request == PAUSE) {
        request = RESUME;
        notify();
    }
}
새로 추가된 두 함수는 request 변수를 적절히 설정하게 된다. 덧붙여, resumeWork()는 현재 대기중인 SearchThread를 깨우기 위해 notify(..) 함수를 호출한다. 해당 쓰레드를 다시 sleep 상태로 만들기 위해 waitIfPauseRequest를 추가한다 :
private void waitIfPauseRequest() 
        throws InterruptedException {
    
    synchronized (this) {
        if (request == PAUSE) {
            while (request != RESUME) {
                wait();
            }

            request = NORMAL;
        }
    }
}
이미 보았듯이, request변수가 PAUSE로 설정되면 쓰레드는 wait함수를 호출한다. Request가 다시 RESUME으로 설정될때까지, 쓰레드는 잠자게 된다. 쓰레드가 인터럽트되면 다시 실행하게 된다. 이 경우는, wait 함수 안에서InterruptedException을 던진 것인데 사용자가 Cancel 버튼을 누른 경우에 해당한다.결과로서 그 쓰레드를 exit 하게 된다. 바로 이 기능이 정확히 우리가 원하는 것이다. 사용자가 Pause또는 Resume 버튼을 클릭하면 프런트 엔드는 단순히 이 함수들을 호출 할 것이다. (해당 버튼들은 적절히 enable/disable됨을 명심하라)
....
} else if (source == pauseButton) {
    if (sThread != null) {
        sThread.pauseWork();
        pauseButton.setEnabled(false);
        resumeButton.setEnabled(true);
    }
} else if (source == resumeButton) {
    if (sThread != null) {
        sThread.resumeWork();
        pauseButton.setEnabled(true);
        resumeButton.setEnabled(false);
    }
}
....
우리는 start, monitor, pause, resume 그리고 stop 기능이 있는 사용자 친화적인 검색기능을 가지는 어플리케이션 만들었다. 여기서 한가지 Pause와 Resume 기능에 관해 주의해야 할 것은 데이터베이스 트랜잭션 안에서 동작하는 작업을 위해 구현하지 말아야 한다는 사실이다. 이런 작업을 Pause 하는 것은 단순히 트랜잭션 시간을 증가시킬뿐더러 다른 트랜잭션에 관계된 쓰레드들이 작업을 멈추고 기다릴 수 있다는 것이다. 트랜잭션 시간 초과와 같은 명백한 단점과는 별도로 성능 역시 명백히 떨어지게 된다.

결론

비록 stop(), pause() , resume()이 deprecated 함수이긴 하지만, 충분히 주의한다면 여전히 이걸 사용할 수도 있다. 이 글은 자바 프로그램 내에서 이 기능들을 어떻게 다루는지를 간단히 보여준다. 그러나 SearchThread는 실제 검색작업과 프런트 엔드와 밀접하게 연결되어 있다. 자세히 들여다보면, 진행사항을 측정 해야 하는 시간-소비 작업(이글에서는 검색 작업)은 반드시 SearchThread안에서 했던 것과 동일한 쓰레드 기능을 사용하도록 해야 한다는 걸 알 수 있다. 더 나은 재사용 측면에서 여러분이 직접 stop, monitor, pause, 와 resume을 처리하는 범용적인 프레임워크를 만들 수도 있을 것이다. 만약 아주 충분히 의욕이 넘친다면, 다중 사용자 기업 환경(Ajax와 서블릿을 사용하는)에서 이러한 기능을 제공하는 시도를 해볼 수도 있을 것이다.

참고자료
본 기사는 담당자의 착오로 기존에 게재된 기사와 같은 원문을 번역한 것입니다.
TAG :
댓글 입력
자료실

최근 본 상품0