Вопрос по android, android-asynctask – Как AsyncTask работает в Android

19

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

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

  1. How many AsyncTask can be started at a time in an Android app?
  2. When I start 10 AsyncTask, will all tasks will run simultaneously or one by one?

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

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

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

Примечание: я проверил это на SDK 4.0

У меня та же проблема, можете ли вы дать какие-либо предложения для моего вопроса стекаstackoverflow.com/questions/17326931/… Karan Mavadhiya

Ваш Ответ

4   ответа
6

чтобы понять его с точки зрения дизайнера и того, как он прекрасно реализовал шаблон проектирования 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 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 Sync Half Async.

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

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

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

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

Таким образом, в функции schedulenext () мы инициализируем mActive объектом Runnable, который мы уже вставили в конец очереди. Этот объект Runnable (который является ничем иным, как mActive) затем выполняется в потоке, взятом из пула потоков. В этом потоке выполняется блок «наконец-то».

Сейчас есть два сценария.

another Asynctask instance has been created and we call the execute method on it when the first task is being executed.

execute method is called for the second time on a same instance of the Asynctask when the first task is getting executed.

Сценарий I: если мы посмотрим на функцию execute Serial Executor, мы обнаружим, что мы фактически создаем новый исполняемый поток (например, поток 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() {
....
}

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

Затем внутри этого потока t мы запускаем функцию запуска mActive. Но, как и в блоке try, finally будет выполняться только после завершения фоновой задачи в этом потоке. (Помните, что и попытаться, и, наконец, происходит в контексте т). Внутри блока finally, когда мы вызываем функцию scheduleNext, mActive становится NULL, потому что мы уже освободили очередь. Однако, если создается другой экземпляр той же Asynctask, и мы вызываем на них команду execute, функция execute этой Asynctask не будет выполнена из-за ключевого слова синхронизации перед execute, а также потому, что 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)");
            }
        }

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

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

Вы можете прочитать мою дискуссию о внутренностях AsynctaskВот.

4

я отложенных задач. Размер очереди по умолчанию равен 10. Например, если вы запускаете 15 ваших задач подряд, то первые 5 введут ихdoInBackground(), но остальные будут ждать в очереди свободного рабочего потока. Когда один из первых 5 завершает работу и освобождает рабочий поток, задача из очереди начнет выполнение. В этом случае не более 5 задач будут выполняться вместе.

Да, существует ограничение на количество запускаемых задач одновременно. Таким образом, AsyncTask использует исполнителя пула потоков с ограниченным максимальным количеством рабочих потоков, а очередь отложенных задач использует фиксированный размер 10. Максимальное количество рабочих потоков - 128. Если вы попытаетесь выполнить более 138 пользовательских задач, ваше приложение выброситRejectedExecutionException.

Ваш ответ полезен. Благодарю. Я хочу знать еще одну вещь. Я запускаю эти 75000 задач в цикле for и не получаю никаких исключений (как вы сказали). Я вызываю task.execute (param) в цикле for, и я не вижу этого исключения. Тогда я не понимаю, когда это число 128 входит в картину? Означает, как я могу воспроизвести ваше исключение, если я выполню более 128 задач? AndroDev
Попробуйте использовать PARALLEL_EXECUTOR и executeOnExecutor (). Не вызывайте метод execute () по умолчанию.
В Android 3.x методtask.executeOnExecutor(Executor exec, Params... params) позволяет использовать пользовательский пул потокового исполнителя и настраивать размер очереди отложенных задач.
32

AsyncTask имеет довольно длинную историю.

Когда он впервые появился в Cupcake (1.5), он обрабатывал фоновые операции с одним дополнительным потоком (один за другим). В Donut (1.6) это было изменено, так что пул потоков начал использоваться. И операции могут обрабатываться одновременно, пока пул не будет исчерпан. В таком случае операции были поставлены в очередь.

Поскольку Honeycomb по умолчанию переключается на использование одного рабочего потока (один за другим). Но новый метод (executeOnExecutor), чтобы дать вам возможность запускать одновременные задачи, если хотите (есть два разных стандартных исполнителя:SERIAL_EXECUTOR а такжеTHREAD_POOL_EXECUTOR).

Способ постановки задач также зависит от того, какого исполнителя вы используете. В случае параллельного вы ограничены 10 (new LinkedBlockingQueue<Runnable>(10)). В случае серийного вы не ограничены (new ArrayDeque<Runnable>()).

То, как ваши задачи обрабатываются, зависит от того, как вы их выполняете и в какой версии SDK вы их запускаете. Что касается ограничений на потоки, то мы не гарантируем ни одного, но, глядя на исходный код ICS, мы можем сказать, что число потоков в пуле может варьироваться в диапазоне5..128.

При запуске 100000 по умолчаниюexecute Используется метод серийного исполнителя. Поскольку задачи, которые не могут быть выполнены немедленно, ставятся в очередь, вы получаетеOutOfMemoryError (тысячи задач добавляются в очередь на основе массива).

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

Спасибо, Раман. Ваш ответ проясняет мои вопросы относительно AsyncTask. AndroDev
Не будет ли он выбрасывать RejectedExecutionException (и не OutOfMemory), если он заполняет рабочую очередь.
Я сделаю это, если вы запустите их с PARALLEL_EXECUTOR. Но в случае с последовательным интерфейсом используется ArrayDequeue. Отредактировал ответ.
LinkedBlockingQueue емкость Android KitKat теперь 128 дляTHREAD_POOL_EXECUTOR,ArrayDeque заSERIAL_EXECUTOR по-прежнему не имеет предела емкости.
1
How many AsyncTask can be started at a time in an Android app?

AsyncTask is backed by a LinkedBlockingQueue with a capacity of 10 (in ICS and gingerbread). So it really depends on how many tasks you are trying to start & how long they take to finish - but it's definitely possible to exhaust the queue's capacity.

When I start 10 AsyncTask, will all tasks will run simultaneously or one by one?

Again, this depends on the platform. The maximum pool size is 128 in both gingerbread and ICS - but the *default behavior* changed between 2.3 and 4.0 - from parallel by default to serial. If you want to execute in parallel on ICS you need to call [executeOnExecutor][1] in conjunction with THREAD_POOL_EXECUTOR

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

Существует жестко заданный верхний предел в 128 потоков - пул потоков больше не будет запускаться, и задача будет помещена в рабочую очередь (максимальный размер которой равен 10). Если эта очередь, в свою очередь, заполнена, задача будет отклонена, и политика выполнения по умолчанию отклоненаAbortPolicy который выбрасывает RejectedExecutionException.
Спасибо за ваш ответ. Я хочу знать еще одну вещь. Как я могу получить RejectExecutionException, когда я запускаю более 128 задач в моем приложении? AndroDev

Похожие вопросы