Ограничения потоков Android AsyncTask? - PullRequest
94 голосов
/ 11 марта 2012

Я разрабатываю приложение, в котором мне нужно обновлять некоторую информацию каждый раз, когда пользователь входит в систему, я также использую базу данных в телефоне.Для всех этих операций (обновления, получение данных из БД и т. Д.) Я использую асинхронные задачи.До сих пор я не понимал, почему я не должен их использовать, но недавно я почувствовал, что если я выполняю некоторые операции, некоторые из моих асинхронных задач просто останавливаются при предварительном выполнении и не переходят к doInBackground.Это было слишком странно, чтобы так все и оставалось, поэтому я разработал еще одно простое приложение, чтобы проверить, что не так.И как ни странно, я получаю то же самое поведение, когда общее количество асинхронных задач достигает 5, 6-е останавливается при предварительном выполнении.

Есть ли у андроида ограничение асинхронных задач в Activity / App?Или это просто какая-то ошибка, о которой нужно сообщить?Кто-нибудь испытывал такую ​​же проблему и, возможно, нашел обходной путь?

Вот код:

Просто создайте 5 из этих потоков для работы в фоновом режиме:

private class LongAsync extends AsyncTask<String, Void, String>
{
    @Override
    protected void onPreExecute()
    {
        Log.d("TestBug","onPreExecute");
        isRunning = true;
    }

    @Override
    protected String doInBackground(String... params)
    {
        Log.d("TestBug","doInBackground");
        while (isRunning)
        {

        }
        return null;
    }

    @Override
    protected void onPostExecute(String result)
    {
        Log.d("TestBug","onPostExecute");
    }
}

А затем создайте эту тему.Он войдет в preExecute и зависнет (он не перейдет в doInBackground).

private class TestBug extends AsyncTask<String, Void, String>
{
    @Override
    protected void onPreExecute()
    {
        Log.d("TestBug","onPreExecute");

        waiting = new ProgressDialog(TestActivity.this);
        waiting.setMessage("Loading data");
        waiting.setIndeterminate(true);
        waiting.setCancelable(true);
        waiting.show();
    }

    @Override
    protected String doInBackground(String... params)
    {
        Log.d("TestBug","doInBackground");
        return null;
    }

    @Override
    protected void onPostExecute(String result)
    {
        waiting.cancel();
        Log.d("TestBug","onPostExecute");
    }
}

Ответы [ 3 ]

205 голосов
/ 11 марта 2012

Все AsyncTasks управляются внутренне с помощью общего (статического) ThreadPoolExecutor и LinkedBlockingQueue . Когда вы вызываете execute в AsyncTask, ThreadPoolExecutor выполнит его, когда будет готов в будущем.

«Когда я буду готов?» поведение ThreadPoolExecutor контролируется двумя параметрами: размер пула ядра и максимальный размер пула . Если в настоящий момент активны потоки с размером меньше основного пула, и приходит новое задание, исполнитель создаст новый поток и сразу же выполнит его. Если запущены потоки хотя бы размера пула, он попытается поставить задачу в очередь и подождать, пока освободится свободный поток (то есть, пока не будет выполнено другое задание). Если невозможно поставить задачу в очередь (очередь может иметь максимальную емкость), она создаст новый поток (потоки с максимальным размером пула) для выполнения заданий. В конечном итоге неосновные незанятые потоки могут быть выведены из эксплуатации. в соответствии с параметром таймаута keep-alive.

До Android 1.6 размер основного пула составлял 1, а максимальный размер пула - 10. Начиная с Android 1.6, размер основного пула - 5, а максимальный размер пула - 128. Размер очереди равен 10 в обоих случаях. , Тайм-аут активности составил 10 секунд до 2,3 и 1 секунду с тех пор.

Имея все это в виду, теперь становится понятно, почему AsyncTask будет казаться только для выполнения 5/6 ваших задач. Шестое задание ставится в очередь до тех пор, пока одно из других заданий не будет выполнено. Это очень веская причина, по которой вам не следует использовать AsyncTasks для длительных операций - это предотвратит запуск других AsyncTasks.

Для полноты, если вы повторили упражнение с более чем 6 задачами (например, 30), вы увидите, что более 6 введут doInBackground, поскольку очередь заполнится, и исполнитель будет вынужден создать больше рабочих потоков. Если вы продолжили выполнять долгосрочное задание, вы должны увидеть, что 20/30 становятся активными, а 10 все еще находится в очереди.

9 голосов
/ 29 августа 2014

@ antonyt имеет правильный ответ, но если вы ищете простое решение, вы можете проверить Иглу.

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

Needle.onBackgroundThread().withThreadPoolSize(3).execute(new UiRelatedTask<Integer>() {
   @Override
   protected Integer doWork() {
       int result = 1+2;
       return result;
   }

   @Override
   protected void thenDoUiRelatedWork(Integer result) {
       mSomeTextView.setText("result: " + result);
   }
});

или что-то вроде

Needle.onMainThread().execute(new Runnable() {
   @Override
   public void run() {
       // e.g. change one of the views
   }
}); 

Это может сделать даже больше.Проверьте это на GitHub .

5 голосов
/ 27 ноября 2016

Обновление : начиная с API 19, размер пула основных потоков был изменен, чтобы отражать количество процессоров на устройстве, с минимумом 2 и максимумом 4 при запуске, при этом увеличиваясь до максимума процессора* 2 +1 - Ссылка

// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;

Также обратите внимание, что в то время как исполнитель AsyncTask по умолчанию является последовательным (выполняет одну задачу за раз и в порядке их поступления), с метод

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
        Params... params)

вы можете предоставить Исполнителю для выполнения ваших задач.Вы можете предоставить THREAD_POOL_EXECUTOR скрытого исполнителя, но без сериализации задач, или вы даже можете создать своего собственного исполнителя и предоставить его здесь.Однако внимательно обратите внимание на предупреждение в Javadocs.

Предупреждение. Разрешать параллельное выполнение нескольких задач из пула потоков обычно не то, что нужно, поскольку порядок их работы не определен.Например, если эти задачи используются для изменения какого-либо общего состояния (например, записи файла из-за нажатия кнопки), нет никаких гарантий порядка изменений.Без тщательной работы в редких случаях более новая версия данных может быть перезаписана более старой, что приведет к неясным проблемам потери и стабильности данных.Такие изменения лучше всего выполнять в сериале;чтобы гарантировать, что такая работа будет сериализована независимо от версии платформы, вы можете использовать эту функцию с SERIAL_EXECUTOR.

Еще одна вещь, на которую следует обратить внимание, это то, что как инфраструктура предоставила Executors THREAD_POOL_EXECUTOR, так и ее последовательную версию SERIAL_EXECUTOR (которая по умолчанию дляAsyncTask) являются статическими (конструкции уровня класса) и, следовательно, совместно используются всеми экземплярами AsyncTask в процессе вашего приложения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...