본문

170925(월) - Sending Operations to Multiple Threads

Sending Operations to Multiple Threads


https://developer.android.com/training/multiple-threads/index.html


Sending Operations to Multiple Threads

- Long running, data-intensive operation의 작업 속도와 효율성은 multiple threads 작업으로 split 할때 향상 될 수 있다.



Specifying the Code to Run on a Thread

- particular operation을 수행하는 1개 이상의 Runnable object를 Task 라고도 부른다.

- Thread와 Runnable은 limited power를 가진 basic class이다.

 HandlerThreadAsyncTask, and IntentService와 같은 Android class의 기반이 된다.

ThreadPoolExecutor의 기반이기도 하다.

ㆍthread와 task queues를 자동으로 관리하고, multiple thread를 parallel 실행 가능하다.


- Define a Class that Implements Runnable

public class PhotoDecodeRunnable implements Runnable {
   
...
   
@Override
   
public void run() {
       
/*
         * Code you want to run on the thread goes here
         */

       
...
   
}
   
...
}

- Implement the run() method

ㆍUI thread 접근하는 바보짓은 하지마라.

Communicate with the UI Thread.

Process.setThreadPriority() withTHREAD_PRIORITY_BACKGROUND를 사용하여 background priority를 설정 가능하다.

ㆍpriority 설정은 Runnable objects의 thread와 UI thread 간의 resource competition을 줄여 준다.

Thread.currentThread().를 call하여 Runnable object thread에 대한 reference를 Runnable에 저장해야 한다.


class PhotoDecodeRunnable implements Runnable {
...
   
/*
     * Defines the code to run for this task.
     */

   
@Override
   
public void run() {
       
// Moves the current Thread into the background
        android
.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
       
...
       
/*
         * Stores the current Thread in the PhotoTask instance,
         * so that the instance
         * can interrupt the Thread.
         */

        mPhotoTask
.setImageDecodeThread(Thread.currentThread());
       
...
   
}
...
}



Creating a Manager for Multiple Threads

- run the task once, may be all need.

- need one execution running at a time, IntentService need.

- pool의 thread가 free가 될때 queue에서 task를 실행하는 thread collection인 ThreadPoolExecutor를 사용하여 resource가 사용가능해지면 자동으로 실행하거나, 동시에 여러 task를 실행 할 수 있다.

- Thread pool은 task의  multiple parallel instances을 실행 가능하므로 thread-safe 인지 확인해야 한다.

synchronized를 사용하여 둘 이상의 thread가 access할 수 있는 variables를 enclose


- Define the Thread Pool Class

ㆍ다른 종류의 Runnable type을 가지고 있는 경우, 각각의 thread pool을 가지고 싶겠지만, single instance 가 된다.

public class PhotoManager {
   
...
   
static  {
       
...
       
// Creates a single static instance of PhotoManager
        sInstance
= new PhotoManager();
   
}
   
...

public class PhotoManager {
   
...
   
/**
     * Constructs the work queues and thread pools used to download
     * and decode images. Because the constructor is marked private,
     * it's unavailable to other classes, even in the same package.
     */

   
private PhotoManager() {
   
...
   
}

public class PhotoManager {
   
...
   
// Called by the PhotoView to get a photo
   
static public PhotoTask startDownload(
       
PhotoView imageView,
       
boolean cacheFlag) {
       
...
       
// Adds a download task to the thread pool for execution
        sInstance
.
                mDownloadThreadPool
.
                execute
(downloadTask.getHTTPDownloadRunnable());
       
...
   
}
private PhotoManager() {
   
...
       
// Defines a Handler object that's attached to the UI thread
        mHandler
= new Handler(Looper.getMainLooper()) {
           
/*
             * handleMessage() defines the operations to perform when
             * the Handler receives a new Message to process.
             */

           
@Override
           
public void handleMessage(Message inputMessage) {
               
...
           
}
       
...
       
}
   
}



- Determine the Thread Pool Parameters

ㆍInitial pool size and maximum pool size

- Thread pool에 넣을 수 있는 thread 수는 주로 device core number에 따라 다르다.

- device의 physical core 수를 반영하지 않을 수 있다.


public class PhotoManager {
...
   
/*
     * Gets the number of available cores
     * (not always the same as the maximum number of cores)
     */

   
private static int NUMBER_OF_CORES =
           
Runtime.getRuntime().availableProcessors();
}

ㆍkeep alive time and time unit

- Thread가 종료되기 전에 idle state로 유지되는 기간

TimeUnit.에 정의된 constants 중에 하나


ㆍA queue of tasks

ThreadPoolExecutor 가 Runnable objects를 받는 incoming queue이다.

- Thread pool 에서 thread 코드를 시작하기 위해서 thread pool manager는 FIFO queue에서 Runnable objects를 가져와서 thread에 연결한다.

 BlockingQueue interface를 implements하는 queue class를 사용하여 thread pool을 만들때 이  queue objects를 제공한다.

Summary of BlockingQueue methods
Throws exceptionSpecial valueBlocksTimes out
Insertadd(e)offer(e)put(e)offer(e, time, unit)
Removeremove()poll()take()poll(time, unit)
Examineelement()peek()not applicablenot applicable


public class PhotoManager {
   
...
   
private PhotoManager() {
       
...
       
// A queue of Runnables
       
private final BlockingQueue<Runnable> mDecodeWorkQueue;
       
...
       
// Instantiates the queue of Runnables as a LinkedBlockingQueue
        mDecodeWorkQueue
= new LinkedBlockingQueue<Runnable>();
       
...
   
}
   
...
}


- Create a Pool of Threads

ㆍThread pool을 만드려면, ThreadPoolExecutor()를 호출하여 thread pool manager를 instance화 한다.

ㆍconstrained thread group을 create and manages 한다.

ㆍinitial pool size and maximum pool size가 동일하므로 ThreadPoolExecutor는 instantiated 될때 모든 thread를 만든다.

private PhotoManager() {
       
...
       
// Sets the amount of time an idle thread waits before terminating
       
private static final int KEEP_ALIVE_TIME = 1;
       
// Sets the Time Unit to seconds
       
private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
       
// Creates a thread pool manager
        mDecodeThreadPool
= new ThreadPoolExecutor(
                NUMBER_OF_CORES
,       // Initial pool size
                NUMBER_OF_CORES
,       // Max pool size
                KEEP_ALIVE_TIME
,
                KEEP_ALIVE_TIME_UNIT
,
                mDecodeWorkQueue
);
   
}



Ruuning Code on a Thread Pool Thread

- thread pool에서의 task run

- task를 pool's work queue에 추가

- thread 가 available하면, ThreadPoolExecutor는 queue에서 task를 가져와서 thread에서 실행

- task가 isn't necessary하면, processor time을 낭비하는 대신 task running thread를 cancel 할 수 있다.


- Run a Task on a Thread in the Thread Pool

ㆍ특정 thread pool의 thread에서 task objects를 시작하려면, Runnable을 ThreadPoolExecutor.execute()에 전달한다.

ㆍthis call은 thread pool의 task queue에 task를 추가

ㆍidle thread가 available 하면, manager는 waiting longest task를 thread 에서  run 한다.

ㆍThreadPoolExecutor는 thread의 Runnable을 시작할때 자동으로 object의 run() method를 call 한다.

public class PhotoManager {
   
public void handleState(PhotoTask photoTask, int state) {
       
switch (state) {
           
// The task finished downloading the image
           
case DOWNLOAD_COMPLETE:
           
// Decodes the image
                mDecodeThreadPool
.execute(
                        photoTask
.getPhotoDecodeRunnable());

           
...
       
}
       
...
   
}
   
...
}


- Interrupt Running Code

ㆍtask를 중지하려면 task's thread를 interrupt 해야한다.

ㆍtask를 만들때, task thread의 handle을 저장해야 한다.

class PhotoDecodeRunnable implements Runnable {
   
// Defines the code to run for this task
   
public void run() {
       
/*
         * Stores the current Thread in the
         * object that contains PhotoDecodeRunnable
         */

        mPhotoTask
.setImageDecodeThread(Thread.currentThread());
       
...
   
}
   
...
}

ㆍthread를 interrupt 하려면, Thread.interrupt()를 call

ㆍThread objects는 System에 의해서 제어되며, app's process outside에서 modify 가능하다.

ㆍ위와같은 이유로 thread interrupt 전에 synchronized block 에 access하여 thread access를 잠글 수 있다.

public class PhotoManager {
   
public static void cancelAll() {
       
/*
         * Creates an array of Runnables that's the same size as the
         * thread pool work queue
         */

       
Runnable[] runnableArray = new Runnable[mDecodeWorkQueue.size()];
       
// Populates the array with the Runnables in the queue
        mDecodeWorkQueue
.toArray(runnableArray);
       
// Stores the array length in order to iterate over the array
       
int len = runnableArray.length;
       
/*
         * Iterates over the array of Runnables and interrupts each one's Thread.
         */

       
synchronized (sInstance) {
           
// Iterates over the array of tasks
           
for (int runnableIndex = 0; runnableIndex < len; runnableIndex++) {
               
// Gets the current thread
               
Thread thread = runnableArray[taskArrayIndex].mThread;
               
// if the Thread exists, post an interrupt to it
               
if (null != thread) {
                    thread
.interrupt();
               
}
           
}
       
}
   
}
   
...
}

ㆍ대부분의 경우 Thread.interrupt()는 즉시 thread를 중지한다.

ㆍbut, waiting thread 만 중지하고 CPU or Network-intensive task는 중단하지 않는다.

ㆍslowing down or locking up the system 상황을 피하고 싶으면, task request 전에 interrupt request test가 필요하다.

/*
 * Before continuing, checks to see that the Thread hasn't
 * been interrupted
 */

if (Thread.interrupted()) {
   
return;
}
...
// Decodes a byte array into a Bitmap (CPU-intensive)
BitmapFactory.decodeByteArray(
        imageBuffer
, 0, imageBuffer.length, bitmapOptions);
...



Communicating with the UI Thread

- task에서 UI thread object로 data를 보내는 방법

- UI thread는 View object와 같은 UI object를 실행하는 special thread

- Thread pool에서 실행하는 task는 UI thread에서 실행되지 않으므로 UI object에 access 불가능

- background thread에서 UI thread로 data를 이동하려면 UI thread에서 실행중인 Handler를 사용


- Define a Handler on the UI Thread

ㆍhandler는 managing therads 하기위한 Android system's framework의 일부

ㆍhandler object는 messages를 receive and handle 하는 코드를 실행

ㆍ보통 new thread handler를 작성하지만, 기존 conneted된 existing thread handler를 작성 가능하다.

ㆍThread pool을 만드는 class의 constructor에서 handler object를 instanciate 하고 object를 global variable에 저장한다.

 Handler(Looper) constructor로 instanciate 해서 UI thread에 연결

ㆍ특정 Looper instance를 기반으로 handler를 instanciate 하면 handler는 looper와 동일한 thread에서 실행된다.

private PhotoManager() {
...
   
// Defines a Handler object that's attached to the UI thread
    mHandler
= new Handler(Looper.getMainLooper()) {
   
...

ㆍHandler 안에있는  handleMessage() method를 override 해서 사용한다.

ㆍAndroid system은 관리하고 있는 thread에게 새로운 message가 오면 이 method를 call

ㆍ특정 thread의 모든 handler object는 같은 message를 받는다.

        /*
         * handleMessage() defines the operations to perform when
         * the Handler receives a new Message to process.
         */

       
@Override
       
public void handleMessage(Message inputMessage) {
           
// Gets the image task from the incoming Message object.
           
PhotoTask photoTask = (PhotoTask) inputMessage.obj;
           
...
       
}
   
...
   
}
}


- Move data in the task object

ㆍbackground thread -> UI thread 로 data를 이동하려면, data 및 UI object에 대한 reference를 task object에 저장해야 한다.

ㆍhandler instanciate object에 task object and status를 전달

ㆍthis object에서 task and status를 포함하는 message를 handler에 보낸다.


ㆍStore data in the task object

- PhotoTask가 ImageView와 Bitmap을 가지고 있다고 하더라도, 현재 UI thread가 아니기때문에 UI object에 assign 불가.

// A class that decodes photo files into Bitmaps
class PhotoDecodeRunnable implements Runnable {
   
...
   
PhotoDecodeRunnable(PhotoTask downloadTask) {
        mPhotoTask
= downloadTask;
   
}
   
...
   
// Gets the downloaded byte array
   
byte[] imageBuffer = mPhotoTask.getByteBuffer();
   
...
   
// Runs the code for this task
   
public void run() {
       
...
       
// Tries to decode the image buffer
        returnBitmap
= BitmapFactory.decodeByteArray(
                imageBuffer
,
               
0,
                imageBuffer
.length,
                bitmapOptions
       
);
       
...
       
// Sets the ImageView Bitmap
        mPhotoTask
.setImage(returnBitmap);
       
// Reports a status of "completed"
        mPhotoTask
.handleDecodeState(DECODE_STATE_COMPLETED);
       
...
   
}
   
...
}
...


ㆍSend status up the object hierarchy

public class PhotoTask {
   
...
   
// Gets a handle to the object that creates the thread pools
    sPhotoManager
= PhotoManager.getInstance();
   
...
   
public void handleDecodeState(int state) {
       
int outState;
       
// Converts the decode state to the overall state.
       
switch(state) {
           
case PhotoDecodeRunnable.DECODE_STATE_COMPLETED:
                outState
= PhotoManager.TASK_COMPLETE;
               
break;
           
...
       
}
       
...
       
// Calls the generalized state method
        handleState
(outState);
   
}
   
...
   
// Passes the state to PhotoManager
   
void handleState(int state) {
       
/*
         * Passes a handle to this task and the
         * current state to the class that created
         * the thread pools
         */

        sPhotoManager
.handleState(this, state);
   
}
   
...
}


ㆍMove data to the UI

public class PhotoManager {
   
...
   
// Handle status messages from tasks
   
public void handleState(PhotoTask photoTask, int state) {
       
switch (state) {
           
...
           
// The task finished downloading and decoding the image
           
case TASK_COMPLETE:
               
/*
                 * Creates a message for the Handler
                 * with the state and the task object
                 */

               
Message completeMessage =
                        mHandler
.obtainMessage(state, photoTask);
                completeMessage
.sendToTarget();
               
break;
           
...
       
}
       
...
   
}

    private PhotoManager() {
       
...
            mHandler
= new Handler(Looper.getMainLooper()) {
               
@Override
               
public void handleMessage(Message inputMessage) {
                   
// Gets the task from the incoming Message object.
                   
PhotoTask photoTask = (PhotoTask) inputMessage.obj;
                   
// Gets the ImageView for this task
                   
PhotoView localView = photoTask.getPhotoView();
                   
...
                   
switch (inputMessage.what) {
                       
...
                       
// The decoding is done
                       
case TASK_COMPLETE:
                           
/*
                             * Moves the Bitmap from the task
                             * to the View
                             */

                            localView
.setImageBitmap(photoTask.getImage());
                           
break;
                       
...
                       
default:
                           
/*
                             * Pass along other messages from the UI
                             */

                           
super.handleMessage(inputMessage);
                   
}
                   
...
               
}
               
...
           
}
           
...
   
}
...
}


'Mobile > Android API' 카테고리의 다른 글

171024(화) - Application Fundamentals  (0) 2017.10.24
171023(월) - Introduction to Android  (0) 2017.10.24
170924(일) - Processes and Threads  (0) 2017.09.24
170613(화) - Android 6.0 Changes  (0) 2017.06.13
170612(월) - Android 6.0 APIs  (0) 2017.06.12

공유

댓글