スレッドプールのスレッドでのコードの実行

前回のガイドでは、複数のスレッド用のマネージャーを作成する方法を説明し、スレッドプールとそこで実行されるタスクを管理するクラスを定義する方法を示しました。このレッスンでは、スレッドプールでタスクを実行する方法について説明します。これを行うには、プールのワークキューにタスクを追加します。スレッドが使用可能になると、ThreadPoolExecutor はキューからタスクを取得し、そのスレッドで実行します。

このレッスンでは、実行中のタスクを停止する方法も説明します。タスクが開始された後、その作業が必要ないことがわかった場合に停止を行うことができます。プロセッサ時間を無駄にせず、タスクが実行されているスレッドをキャンセルできます。たとえば、ネットワークから画像をダウンロードしてキャッシュを使用している場合、画像がすでにキャッシュに存在していることを検出したときに、タスクを停止できます。アプリの作成方法によっては、ダウンロード開始前には検出できない場合があるためです。

スレッドプール内のスレッドでタスクを実行する

特定のスレッドプールのスレッドでタスク オブジェクトを開始するには、RunnableThreadPoolExecutor.execute() に渡します。この呼び出しにより、タスクがスレッドプールのワークキューに追加されます。アイドル状態のスレッドが使用可能になると、マネージャーは最も早くから待機していたタスクを取得し、スレッドで実行します。次に例を示します。

Kotlin

    object PhotoManager {

        fun handleState(photoTask: PhotoTask, state: Int) {
            when (state) {
                DOWNLOAD_COMPLETE ->
                    // Decodes the image
                    decodeThreadPool.execute(photoTask.getPhotoDecodeRunnable())
                ...
            }
            ...
        }
        ...
    }
    

Java

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

ThreadPoolExecutor は、スレッドで Runnable を起動すると、オブジェクトの run() メソッドを自動的に呼び出します。

実行中のコードに割り込む

タスクを停止するには、タスクが実行されているスレッドを中断する必要があります。この中断を準備するには、タスクの作成時にタスクのスレッドへのハンドルを保存する必要があります。次に例を示します。

Kotlin

    class PhotoDecodeRunnable : Runnable {

        // Defines the code to run for this task
        override fun run() {
            /*
             * Stores the current Thread in the
             * object that contains PhotoDecodeRunnable
             */
            photoTask.setImageDecodeThread(Thread.currentThread())
            ...
        }
        ...
    }
    

Java

    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
             */
            photoTask.setImageDecodeThread(Thread.currentThread());
            ...
        }
        ...
    }
    

スレッドを中断するには、Thread.interrupt() を呼び出します。Thread オブジェクトはシステムによって制御されているため、アプリのプロセス外でそれらを変更できることに注意してください。このため、スレッドを中断する前に、アクセスを synchronized ブロックに配置して、スレッドへのアクセスをロックする必要があります。次に例を示します。

Kotlin

    object PhotoManager {
        fun cancelAll() {
            /*
             * Creates and populates an array of Runnables with the Runnables in the queue
             */
            val runnableArray: Array<Runnable> = decodeWorkQueue.toTypedArray()
            /*
             * Iterates over the array of Runnables and interrupts each one's Thread.
             */
            synchronized(this) {
                // Iterates over the array of tasks
                runnableArray.map { (it as? PhotoDecodeRunnable)?.mThread }
                        .forEach { thread ->
                            thread?.interrupt()
                        }
            }
        }
        ...
    }
    

Java

    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[decodeWorkQueue.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
                    Runnable runnable = runnableArray[runnableIndex];
                    Thread thread = null;
                    if (runnable instanceof PhotoDecodeRunnable) {
                        thread = ((PhotoDecodeRunnable)runnable).mThread;
                    }
                    // if the Thread exists, post an interrupt to it
                    if (null != thread) {
                        thread.interrupt();
                    }
                }
            }
        }
        ...
    }
    

ほとんどの場合、Thread.interrupt() はスレッドを即座に停止します。ただし、停止するのは待機中のスレッドのみで、CPU またはネットワーク集中型のタスクは中断しません。システムのスローダウンまたはロックアップを回避するには、操作を試みる前に保留中の割り込みリクエストをテストする必要があります。次に例を示します。

Kotlin

    /*
     * 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.size, bitmapOptions)
    ...
    

Java

    /*
     * 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);
    ...
    

詳細

Android でのマルチスレッド操作の詳細については、プロセスとスレッドの概要ガイドをご覧ください。

サンプルアプリ

このガイドのコンセプトを試すには、ThreadSample をダウンロードしてください。