AsyncTask의 우선순위 조정하기..

안드로이드 진저브레드 이후부터는 태스크를 처리하는 형태로 AsyncTask를 사용하도록 권장한다. 만약에 개발하는 앱이, 서버에서 100장의 사진을 수신하는 상황을 생각해 보자.

시나리오는 다음과 같다.

  1. 사진의 목록을 가져온다.
  2. 목록이 보이기 시작하면 한 장씩 사진을 웹에서 가져온다.

대부분의 앱은 이 시나리오를 기준으로 개발할 것이고, 목록을 가져오는 태스크와 사진을 한 장씩 가져오는 태스크를 처리하는 데 AsyncTask를 사용한다고 가정한다.

이 시나리오대로 개발한 앱에서, 화면을 빠르게 스크롤 하면 개별 사진을 가져오는 AsyncTask가 많이 생성된다. 그리고 페이징으로 목록을 가져오는 경우에는 목록을 가져오는 AsyncTask도 경쟁상황(Race Condition)이 돼서 목록을 보는 데 오랜 시간이 걸리는 경우가 발생할 수 있다.

이 상황에서 더 좋은 UX를 위해서 AsyncTask의 우선순위를 조정해서, 목록을 가져오는 태스크의 우선순위를 다른 태스크보다 높이는 방법에 대해서 살펴보자.

AsyncTask는 태스크를 비동기 스레드로 처리하도록 스레드와 핸들러를 추상화한 클래스이다. 그리고 Template Method 패턴과 Command 패턴의 형태를 보이고 있다. AsyncTask가 스레드 말하시는 분들이 있는데, AsyncTask 소스(https://github.com/android/platform_frameworks_base/blob/master/core/java/android/os/AsyncTask.java)를 보면, 맞기도 하지만 틀리기도 하다. 정확한 표현은 태스크를 처리하는 스레드 디스패처라고 봐야 하겠다. 하지만 AsyncTask를 상속해서 사용하게 되면, 이 스레드 디스패처를 사용하는 태스크 처리 스레드라고 보면 좀 명확(?) 하겠다.

AsyncTask는 static으로 스레드를 디스패칭하는 Executor가 있고, 이 객체가 스레드를 실행시킨다. 이 구조는 자바의 병행(Concurrent) 스레드 처리하는 형태와 같다. AsyncTask 우선순위를 조정하기 위해서 첫 번째로, 생성자 부분을 살펴보자.

public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
 
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                return postResult(doInBackground(mParams));
            }
        };
 
        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occured while executing doInBackground()", e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

이 생성자에서 Worker 스레드의 우선순위를 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 로 조정하는 것을 알 수 있다.

이 정보를 기준으로 간단하게 새로운 AsyncTask 클래스를 사용하는 방법을 생각해 보자. 거의 동일한 클래스이고, 위의  Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); 로 조정한 새로운 클래스를 만들고, 이 클래스를 상속해서 사용하면 기존의 AsyncTask보다 빠르게 태스크를 처리하는 AsyncTask를 사용할 수 있다.

PriorityAsyncTask.java

그럼, 이 PriorityAsyncTask를 상속받은 클래스와 기존의 AsyncTask를 상속받은 클래스의 스레드 스케줄링에 대해서 테스트 해 보자.

	
package net.sjava.asynctest;

import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;

public class MainActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		e("MAIN THREAD PRIORITY : " + Thread.currentThread().getPriority());
		
		new BackgroundTask(1).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");
		new BackgroundTask(2).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");
		new BackgroundTask(3).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");
		new BackgroundTask(4).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");
		new BackgroundTask(5).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");
		new BackgroundTask(6).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");
		new BackgroundTask(7).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");
		new BackgroundTask(8).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");
		new ForgroundTask().executeOnExecutor(PriorityAsyncTask.THREAD_POOL_EXECUTOR, "");
		new BackgroundTask(9).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");
		new BackgroundTask(10).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");
	}


	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		return true;
	}
	// 
	public void w(String value) {
		android.util.Log.w("ThreadTest", value);
	}
	
	public void e(String value) {
		android.util.Log.e("ThreadTest", value);
	}
	
	int size = 1000000;
	class BackgroundTask extends AsyncTask<String, Integer, Boolean> {
		int x =0;
		long value = 0L;
		BackgroundTask(int x) {
			this.x = x;
		}
		
		@Override
		protected Boolean doInBackground(String... params) {
			long start = System.currentTimeMillis();
			w(x + ", BackgroundTask start : " + start );
			for(int i=0; i < size; i++) {
				value += i;
			}
			
			w(x +", BackgroundTask ended : " + value +" : " + (System.currentTimeMillis() - start));
			return null;
		}
	}
	
	class ForgroundTask extends PriorityAsyncTask<String, Integer, Boolean> {
		long value = 0L;
		
		@Override
		protected Boolean doInBackground(String... params) {
			long start = System.currentTimeMillis();
			w("ForgroundTask start : " + start );
			
			for(int i=0; i < size; i++) {
				value += i;
			}
			
			w("ForgroundTask ended : " + value +" : "+ (System.currentTimeMillis() - start));
			return null;
		}	
	}	
}

위 코드의 결과는 다음과 같다.

06-04 21:37:41.043: W/ThreadTest(7353): 1, BackgroundTask start : 1370381861052
06-04 21:37:41.053: W/ThreadTest(7353): 2, BackgroundTask start : 1370381861053
06-04 21:37:41.063: W/ThreadTest(7353): 4, BackgroundTask start : 1370381861066
06-04 21:37:41.063: W/ThreadTest(7353): 5, BackgroundTask start : 1370381861072
06-04 21:37:41.083: W/ThreadTest(7353): 3, BackgroundTask start : 1370381861083
06-04 21:37:41.133: W/ThreadTest(7353): 4, BackgroundTask ended : 499999500000 : 75
06-04 21:37:41.143: W/ThreadTest(7353): 1, BackgroundTask ended : 499999500000 : 98
06-04 21:37:41.163: W/ThreadTest(7353): 2, BackgroundTask ended : 499999500000 : 113
06-04 21:37:41.163: W/ThreadTest(7353): 6, BackgroundTask start : 1370381861169
06-04 21:37:41.172: W/ThreadTest(7353): 3, BackgroundTask ended : 499999500000 : 93
06-04 21:37:41.172: W/ThreadTest(7353): 7, BackgroundTask start : 1370381861176
06-04 21:37:41.172: W/ThreadTest(7353): 8, BackgroundTask start : 1370381861178
06-04 21:37:41.202: W/ThreadTest(7353): 5, BackgroundTask ended : 499999500000 : 132
06-04 21:37:41.202: W/ThreadTest(7353): 9, BackgroundTask start : 1370381861204
06-04 21:37:41.202: W/ThreadTest(7353): ForgroundTask start : 1370381861207
06-04 21:37:41.252: W/ThreadTest(7353): ForgroundTask ended : 499999500000 : 47
06-04 21:37:41.262: W/ThreadTest(7353): 10, BackgroundTask start : 1370381861266
06-04 21:37:41.322: W/ThreadTest(7353): 6, BackgroundTask ended : 499999500000 : 155
06-04 21:37:41.322: W/ThreadTest(7353): 10, BackgroundTask ended : 499999500000 : 62
06-04 21:37:41.333: W/ThreadTest(7353): 7, BackgroundTask ended : 499999500000 : 158
06-04 21:37:41.333: W/ThreadTest(7353): 8, BackgroundTask ended : 499999500000 : 157
06-04 21:37:41.333: W/ThreadTest(7353): 9, BackgroundTask ended : 499999500000 : 132

이 결과를 보면, AsyncTask를 상속한 BackgroundTask의 태스크는 경쟁상황에서 태스크가 실행되고 있지만, 이 PriorityAsyncTask를 상속받은 ForgroundTask의 태스크는 실행이 되자마자 바로 실행을 마치는 것을 알 수 있다.

이상, AsyncTask의 우선순위를 조정하는 방법에 대해서 살펴 보았다. 만약 태스크에 대한 스케줄링을 고민한다면, http://www.sjava.net/312 도 참조해 보세요.

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.