Únete a ⁠ #Android11: The Beta Launch Show el 3 de junio.

Cómo ejecutar código en un conjunto de subprocesos

En la guía anterior, en la que se explica Cómo crear un administrador para varios subprocesos, se muestra cómo definir una clase que administra conjuntos de subprocesos y las tareas que se ejecutan en ellos. En esta lección, se muestra cómo ejecutar una tarea en un conjunto de subprocesos. Para hacerlo, debes agregar la tarea a la cola de trabajo del conjunto. Cuando un subproceso está disponible, ThreadPoolExecutor toma una tarea de la cola y la ejecuta en el subproceso.

En esta lección, también se muestra cómo detener una tarea en ejecución. Es posible que quieras hacerlo si se inicia una tarea, pero luego se descubre que no es necesaria. En lugar de malgastar el tiempo del procesador, puedes cancelar el subproceso en el que se ejecuta la tarea. Por ejemplo, si descargas imágenes de la red con una caché, es probable que quieras detener una tarea si esta detecta que ya hay una imagen en la caché. Según cómo escribas tu app, es posible que no puedas detectar esta situación antes de iniciar la descarga.

Cómo ejecutar una tarea en un subproceso del conjunto de subprocesos

Para iniciar un objeto de tarea en un subproceso de un grupo de subprocesos en particular, pasa Runnable a ThreadPoolExecutor.execute(). Esta llamada agrega la tarea a la cola de trabajo del conjunto de subprocesos. Cuando se habilita un subproceso inactivo, el administrador toma la tarea con el mayor tiempo de espera y la ejecuta en el subproceso:

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());
                ...
            }
            ...
        }
        ...
    }
    

Cuando ThreadPoolExecutor inicia un objeto Runnable en un subproceso, llama automáticamente al método run() del objeto.

Cómo interrumpir un código en ejecución

Si quieres detener una tarea, deberás interrumpir el subproceso de la tarea. A modo de preparación, deberás almacenar un controlador para el subproceso de la tarea cuando la crees. Por ejemplo:

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());
            ...
        }
        ...
    }
    

Para interrumpir un subproceso, llama a Thread.interrupt(). Ten en cuenta que el sistema controla los objetos Thread y puede modificarlos fuera del proceso de tu app. Por este motivo, debes bloquear el acceso a un subproceso antes de interrumpirlo. Para ello, coloca el acceso en un bloque synchronized. Por ejemplo:

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();
                    }
                }
            }
        }
        ...
    }
    

En la mayoría de los casos, Thread.interrupt() detiene el subproceso de inmediato. Sin embargo, solo detiene los subprocesos que están en cola y no interrumpirá las tareas de uso intensivo de la CPU o de red. Para evitar ralentizar o bloquear el sistema, deberías probar cualquier solicitud de interrupción pendiente antes de intentar una operación:

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

Más información

Para obtener más información sobre las operaciones de varios subprocesos en Android, consulta la siguiente guía: Descripción general de procesos y subprocesos.

App de ejemplo

Para probar los conceptos de esta guía, descarga ThreadSample.