태그 보관물: RejectedExecutionException

Android java.util.concurrent.RejectedExecutionException 핸들링(2/2)..

앞에서 Android java.util.concurrent.RejectedExecutionException 핸들링(1/2).. 하기라는 포스팅에서 안드로이드에서 상당히 많은 쓰레드를 단기간에 실행(보통 안드로이드 개발가이드만 보면 유발 가능)시키면 RejectedExecutionException이 발생하고 이것에 대한 간단한 해결책을 기술했습니다. 그 내용과 더불어서 위 상황을 해결할 수 있는 방법으로, 좀 더 편리한 방법을 기술해 봅니다.

이 방법은 위 링크에서 설명하고 있는 ExecutorService가 실행하는 방식과 비슷한 Producer Consumer 패턴을 이용해서 해결할 수 있습니다. Producer는 간단하게 AsyncTask를 실행할 데이터를 Queue에 입력하는 코드가 되겠고, Consumer는 Queue에 들어간 데이터를 가져와서 AsyncTask를 실행하는 쓰레드입니다..

아래 코드는 실시간으로 바로 응답이 필요한 Task보다는, 한꺼번에 많은 Task를 Queue에 담았다가 약간 지연(위의 Exception 상황 회피)를 주면서 처리하는 코드에 유용하게 사용할 수 있습니다.

아래 PhotoDispatcher 클래스는, 위의 설명대로 Queue와 Consumer를 유지하면서, Queue에 처리할 데이터가 들어오면 Consumer가 Queue에서 데이터를 가져와서 AsyncTask를 실행시킵니다. ^^

public class PhotoDispatcher {
    static String CNAME = PhotoDispatcher.class.getSimpleName();
    static BlockingQueue<Element> queue = new ArrayBlockingQueue<Element>(500);
    static ArrayList<Thread> consumers = new ArrayList<Thread>(2); // 쓰레드 2개
   
    static {
        consumers.add(new Thread(new Consumer(“1”)));
        consumers.add(new Thread(new Consumer(“2”)));
       
        consumers.get(0).start();
        consumers.get(1).start();
    }
       
    public static void put(PhotoElement e) {
        if(e == null)
            return;
       
        try {
            queue.put(e);
        } catch (InterruptedException e1) {
            LogUtil.e(CNAME, LogUtil.TAG, ExceptionUtil.getException(e1));
        }
    } 
   
    public static class Consumer implements Runnable {
        private String name = “”;
        public Consumer(String name) {
            this.name = name;
        }
       
        @Override
        public void run() {
            while(true) {
                try {
                    Element element = queue.take();
                   
                    if(IS_DEBUG) {
                        LogUtil.w(CNAME, LogUtil.TAG, “NAME : ” + name);
                        LogUtil.w(CNAME, LogUtil.TAG, “ELEMENT : ” + element.toString());
                        LogUtil.w(CNAME, LogUtil.TAG, “URL : ” + AuthService.thumbnailServerUrl);
                    }
                   
                    if(element != null) {
                        // 썸네일 가져오는 Task
                        new GetThumbnailTask(element.c, element.iv, element.fileId).execute(element.fileId);
                    }
                   
                    // 쓰레드를 0.5초 딜레이 시킨다.
                    Thread.sleep(500);
                } catch(Exception e) {
                    LogUtil.e(CNAME, LogUtil.TAG, ExceptionUtil.getException(e));
                }
            }
        }
    }
}

   

Android java.util.concurrent.RejectedExecutionExecution 핸들링(1/2)..

안드로이드 API의 AsyncTask는 API Level 3부터 지원하는 클래스로, UI 스레드가 아닌 스레드(백그라운드 스레드)를 생성하고 실행한 결과를 UI 스레드에 전달(Looper라는 놈이 전달함)하는 래퍼이다. AsyncTask를 사용하는 방법은 이 클래스를 상속해서 doInBackground() 메서드를 구현하면 된다. 아래 내용은 안드로이드 2.2 기반에서의 경험한 내용이다.

그래고, 많은 데이터를 가지고 오기 위해서 페이징을 합니다. 안드로이드는 하단에 spinner와 Loading과 같은 메세지가 표준처럼 사용되고 있죠.

사진에 대한 Thumbnail이 매우 많고, 30개씩 가져온다고 가정을 합니다.. 보통 ArrayAdapter나 ListAdapter를 상속받아서 Adapter 객체를 만들고, Adapter 객체의 getView()에서 랜더링을 위한 작업을 구현하게 됩니다.

@Override
public View getView(int position, View cView, ViewGroup parent) {
}

Thumbnail을 가져와야 하니, 보통 위 메소드 안에서 Thumbnail을 가져오기 위해, AsyncTask를 상속받은 쓰레드로 처리를 하는 것이 일반적입니다. 이 상황은 쓰레드를 많이 생성하게 됩니다..

위의 경우에 RejectedExecutionException 이 발생할 수 있습니다.. 안드로이드는 내부적으로 java.util.concurrent 패키지의 ExecutorService를 Thread 디스패칭에 사용하고 있습니다. 따라서, 요 문제는 아래의 ExecutorService가 처리해야 하는 AsyncTask의 POOL_SIZE가 순간적으로 넘어서고, 이 경우에 AsyncTask가 풀에 들어갈 수 없어서 발생하는 익셉션입니다. 위 문제는 빈번하게 발생하지 않을 것 같지만, 게임이라던지 쓰레드가 많이 필요한 작업을 하다 보면 발생할 수 있습니다..

아래는 Android 플랫폼에서 기본적으로 가지고 있는 AsyncTask의 정책값입니다.

private static final int CORE_POOL_SIZE = 1;
private static final int MAXIMUM_POOL_SIZE = 10;
private static final int KEEP_ALIVE = 10;

따라서, 위 문제는 하나의 쓰레드로 30개의 이미지를 가져오는 형태로 살짝 바꾸면 해결이 됩니다. 당근 성능과 안전성의 트레이드 오프라고 할 수 있겠습니다..

위에서 간단하게 RejectedExecutionException을 피해갈 수 있는 방안에 대해서 살펴보았습니다..
위에서 고려한 사진의 Thumbnail에 대한 예로, 한번 더 고민을 해 보면, 만약에 30개를 가져오는 쓰레드를 getView()외에서 처리를 한다면, 지금 보고 있는 화면에서 사진이 안 보일 겁니다.. 왜냐하면, getView()에서 처리를 하지 않았기 때문에 빈 사진만 보이겠죠.

그래서, 위 가정대로, 사진이 많아서 페이징을 해야하고 Thumbnail을 보여준다면, 사진을 가져오는 첫 페이지에서는 getView()에서 AsyncTask를 불러서 사진을 가져오고, 두 번째 페이지 부터는 데이터를 가져와서 Adapter에 바인딩하는 쓰레드가 30개의 사진을 처리하는 쓰레드를 다시 부르는 형태로 구현을 하는 것이 좋을 것 같습니다..