Как работает AsyncTask на Android


1 Answers

давайте погрузимся глубоко в файл Asynctask.java Android, чтобы понять его с точки зрения дизайнера и как он хорошо реализовал шаблон дизайна Half Sync-Half Async.

В начале класса несколько строк кода выглядят следующим образом:

 private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };



 private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(10);

    /**
    * An {@link Executor} that can be used to execute tasks in parallel.
    */
    public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

Первый - ThreadFactory, который отвечает за создание рабочих потоков. Членная переменная этого класса представляет собой количество потоков, созданных до сих пор. В тот момент, когда он создает рабочий поток, это число увеличивается на 1.

Следующий - BlockingQueue. Как вы знаете из документации по блокировке Java, она фактически обеспечивает поточную безопасную синхронизированную очередь, реализующую логику FIFO.

Следующим является исполнитель пула потоков, который отвечает за создание пула рабочих потоков, которые могут быть приняты по мере необходимости для выполнения различных задач.

Если мы посмотрим на первые несколько строк, мы узнаем, что Android ограничил максимальное число потоков 128 (как видно из частного статического final int MAXIMUM_POOL_SIZE = 128).

Теперь следующим важным классом является SerialExecutor, который был определен следующим образом:

private static class SerialExecutor implements Executor {
       final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
       Runnable mActive;

       public synchronized void execute(final Runnable r) {
           mTasks.offer(new Runnable() {
               public void run() {
                   try {
                       r.run();
                   } finally {
                       scheduleNext();
                   }
               }
           });
           if (mActive == null) {
               scheduleNext();
           }
       }

       protected synchronized void scheduleNext() {
           if ((mActive = mTasks.poll()) != null) {
               THREAD_POOL_EXECUTOR.execute(mActive);
           }
       }
   }

Следующие важные две функции в Asynctask

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

а также

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        mStatus = Status.RUNNING;

        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }

Как видно из приведенного выше кода, мы можем вызвать executeOnExecutor из функции exec Asynctask, и в этом случае он принимает исполнитель по умолчанию. Если мы выкопаем исходный код Asynctask, мы обнаружим, что этот исполнитель по умолчанию - это не что иное, как последовательный исполнитель, код которого приведен выше.

Теперь давайте вникаем в класс SerialExecutor. В этом классе мы имеем окончательный ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();.

Это фактически работает как сериализатор различных запросов в разных потоках. Это пример Half Half Sync Half Async.

Теперь рассмотрим, как это делает последовательный исполнитель. Посмотрите на часть кода SerialExecutor, которая написана как

 if (mActive == null) {
                scheduleNext();
            }

Поэтому, когда выполнение сначала вызывается в Asynctask, этот код выполняется в основном потоке (поскольку mActive будет инициализирован NULL), и, следовательно, это приведет нас к функции scheduleNext (). Функция ScheduleNext () была записана следующим образом:

protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }

Таким образом, в функции schedulenext () мы инициализируем mActive с помощью объекта Runnable, который мы уже вставили в конце dequeue. Этот объект Runnable (который не что иное, как mActive) затем выполняется в потоке, взятом из threadpool. В этом потоке выполняется блок finally.

Теперь есть два сценария.

  1. был создан другой экземпляр Asynctask, и мы вызываем метод execute на нем при выполнении первой задачи.

  2. метод execute вызывается во второй раз в том же экземпляре Asynctask, когда выполняется первая задача.

Сценарий I: если мы посмотрим на функцию выполнения Serial Executor, мы обнаружим, что мы фактически создаем новый runnable thread (Say thread t) для обработки фоновой задачи. Посмотрите следующий фрагмент кода:

 public synchronized void execute(final Runnable r) {
           mTasks.offer(new Runnable() {
               public void run() {
                   try {
                       r.run();
                   } finally {
                       scheduleNext();
                   }
               }
           });

Как становится ясно из строки mTasks.offer(new Runnable) , каждый вызов функции execute создает новый рабочий поток. Теперь, вероятно, вы можете узнать сходство между Half Sync - Half Async и функционированием SerialExecutor. Позвольте мне, однако, прояснить сомнения. Так же, как асинхронный слой Half Sync - Half Async,

mTasks.offer(new Runnable() {
....
}

часть кода создает новый поток, вызываемый функцией выполнения функции, и выталкивает ее в очередь (mTasks). Это делается абсолютно асинхронно, так как момент, когда он вставляет задачу в очередь, возвращает функцию. И тогда фоновый поток выполняет задачу синхронно. Таким образом, он похож на Half Sync - Half Async. Правильно?

Затем внутри этого потока t мы запускаем функцию запуска mActive. Но, как и в блоке try, окончательно будет выполняться только после завершения фоновой задачи в этом потоке. (Помните обе попытки и, наконец, происходит в контексте t). Внутри блока finally, когда мы вызываем функцию scheduleNext, mActive становится NULL, потому что мы уже опустошили очередь. Однако, если создается другой экземпляр той же Asynctask, и мы вызываем execute на них, функция выполнения этих Asynctask не будет выполняться из-за ключевого слова синхронизации перед выполнением, а также потому, что SERIAL_EXECUTOR является статическим экземпляром (следовательно, все объекты одного и того же класса будет совместно использовать один и тот же экземпляр ... его пример блокировки уровня класса), я имею в виду, что ни один экземпляр того же класса Async не может вытеснить фоновую задачу, которая выполняется в потоке t. и даже если поток прерывается некоторыми событиями, блок finally, который снова вызывает функцию scheduleNext (), позаботится об этом. Все это означает, что будет выполняться только один активный поток, выполняющий задачу. этот поток не может быть одинаковым для разных задач, но только один поток за раз выполнит задачу. поэтому последующие задачи будут выполняться один за другим только после завершения первой задачи. вот почему он называется SerialExecutor.

Сценарий II: В этом случае мы получим ошибку исключения. Чтобы понять, почему функция execute не может вызываться более одного раза на одном и том же объекте Asynctask, ознакомьтесь с нижеприведенным фрагментом кода, взятым из функции executorOnExecute из Asynctask.java, особенно в приведенной ниже части:

 if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

AS из приведенного выше фрагмента кода становится ясно, что если мы дважды вызываем функцию выполнения, когда задача находится в текущем состоянии, она выдает исключение IllegalStateException «Невозможно выполнить задачу: задача уже запущена».

если мы хотим, чтобы несколько задач выполнялись параллельно, нам нужно вызвать execOnExecutor, передающий Asynctask.THREAD_POOL_EXECUTOR (или, возможно, пользовательский параметр THREAD_POOL в качестве параметра exec.

Здесь вы можете прочитать мое обсуждение о внутренних функциях Asynctask .

Question

Я хочу знать, как работает AsyncTask внутри.

Я знаю, что он использует Java Executor для выполнения операций, но все же некоторые из вопросов, которые я не понимаю. Подобно:

  1. Сколько AsyncTask можно запустить одновременно в приложении для Android?
  2. Когда я запустил 10 AsyncTask, все задачи будут выполняться одновременно или по одному?

Я попытался с 75000 AsyncTask проверить те же самые. У меня нет никаких проблем и кажется, что все задачи будут перенесены в стек и будут запускаться один за другим.

Также, когда я запускаю 100000 AsyncTasks, я начинаю получать OutOfMemoryError.

Так есть ли какой-либо предел AsyncTask, который может быть запущен за раз?

Примечание. Я тестировал их на SDK 4.0




  1. Сколько AsyncTask можно запустить одновременно в приложении для Android?

    AsyncTask поддерживается LinkedBlockingQueue с емкостью 10 (в ICS и пряниках). Так что это действительно зависит от того, сколько задач вы пытаетесь запустить и как долго они завершат - но это, безусловно, возможно исчерпать возможности очереди.

  2. Когда я запустил 10 AsyncTask, все задачи будут выполняться одновременно или по одному?

    Опять же, это зависит от платформы. Максимальный размер пула - 128 в обоих имбирных и ICS - но поведение * по умолчанию * изменилось между 2,3 и 4.0 - от параллельного по умолчанию до серийного. Если вы хотите выполнить параллельно в ICS, вам необходимо вызвать [executeOnExecutor] [1] в сочетании с THREAD_POOL_EXECUTOR

Попробуйте переключиться на параллельный исполнитель и спам его с 75 000 задач - последовательный имп. имеет внутренний ArrayDeque который не имеет ограничений на верхнюю емкость (кроме OutOfMemoryExceptions ofc).




Related