Запуск нескольких AsyncTasks одновременно - не возможно?

259

Я пытаюсь запустить две AsyncTasks одновременно. (Платформа Android 1.5, HTC Hero.) Однако выполняется только первое. Вот простой фрагмент, чтобы описать мою проблему:

public class AndroidJunk extends Activity {
 class PrinterTask extends AsyncTask<String, Void, Void> {
     protected Void doInBackground(String ... x) {
      while (true) {
       System.out.println(x[0]);
       try {
        Thread.sleep(1000);
       } catch (InterruptedException ie) {
        ie.printStackTrace();
       }
      }
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        new PrinterTask().execute("bar bar bar");
        new PrinterTask().execute("foo foo foo");

        System.out.println("onCreate() is done.");
    }
}

Результат, который я ожидаю:

onCreate() is done.
bar bar bar
foo foo foo
bar bar bar
foo foo foo

И так далее. Однако, что я получаю, это:

onCreate() is done.
bar bar bar
bar bar bar
bar bar bar

Второй AsyncTask никогда не выполняется. Если я изменю порядок операторов execute (), только задача foo выдаст результат.

Я что-то упускаю здесь очевидное и / или делаю что-то глупое? Разве невозможно выполнить две AsyncTasks одновременно?

Редактировать: я понял, что телефон работает под управлением Android 1.5, я обновил описание проблемы. соответственно. У меня нет этой проблемы с HTC Hero под управлением Android 2.1. Хм ...

Rodion
источник
Ваш код работает для меня, поэтому проблема должна быть где-то еще. Вы ввели фильтр в вашем представлении LogCat? ;-)
mreichelt
Хм, это странно. У меня нет никакой фильтрации в logcat. Вы тоже используете 1.6? Если да, то какой телефон?
Родион
Ой, только что понял, что это работает (древний) Android 1.5
Родион
1
Я использовал Android 1.6 в качестве цели и эмулятор Android 2.1. Так что, если проблема действительно возникает на HTC Hero только с Android 1.5 - вверните их, все в порядке. ;-) В HTC Hero уже есть обновление до новой версии Android. Я не стал бы беспокоиться об этом, если бы были некоторые производители, которые все испортили Кроме того, я бы не стал больше беспокоиться об Android 1.5.
mreichelt
AsyncTask следует использовать для задач с более короткой продолжительностью 5 мс. Перейти к ThreadPoolExecutor ( developer.android.com/reference/java/util/concurrent/… ). Связанный пост: stackoverflow.com/questions/6964011/…
Равиндра Бабу

Ответы:

438

AsyncTask использует шаблон пула потоков для запуска вещи из doInBackground (). Изначально проблема заключалась в том, что в ранних версиях ОС Android размер пула составлял всего 1, что означает отсутствие параллельных вычислений для группы AsyncTasks. Но позже они исправили это, и теперь размер равен 5, поэтому максимум 5 AsyncTasks могут работать одновременно. К сожалению, я не помню, в какой версии именно они это изменили.

ОБНОВИТЬ:

Вот что говорит текущий API (2012-01-27) об этом:

При первом представлении AsyncTasks выполнялись последовательно в одном фоновом потоке. Начиная с DONUT, это было изменено на пул потоков, позволяющий нескольким задачам работать параллельно. После HONEYCOMB планируется изменить это обратно на один поток, чтобы избежать распространенных ошибок приложения, вызванных параллельным выполнением. Если вы действительно хотите параллельное выполнение, вы можете использовать версию этого метода executeOnExecutor (Executor, Params ...) с THREAD_POOL_EXECUTOR; однако, см. комментарий там для предупреждений относительно его использования.

DONUT - это Android 1.6, HONEYCOMB - это Android 3.0.

ОБНОВЛЕНИЕ: 2

Смотрите комментарий kabukoот Mar 7 2012 at 1:27.

Оказывается, что для API, в которых используется «пул потоков, позволяющий нескольким задачам работать параллельно» (начиная с 1.6 и заканчивая 3.0), количество одновременно выполняемых AsyncTasks зависит от того, сколько задач уже передано для выполнения, но еще не закончили их doInBackground().

Это проверено / подтверждено мной на 2.2. Предположим, у вас есть пользовательский AsyncTask, который просто спит секунду doInBackground(). AsyncTasks использует очередь фиксированного размера для хранения отложенных задач. Размер очереди по умолчанию равен 10. Если вы запускаете 15 своих пользовательских задач подряд, то первые 5 будут вводить их doInBackground(), а остальные будут ждать в очереди свободного рабочего потока. Как только любой из первых 5 завершается и, таким образом, освобождает рабочий поток, задача из очереди начнет выполнение. Таким образом, в этом случае не более 5 задач будут выполняться одновременно. Однако, если вы запустите 16 своих пользовательских задач подряд, то первые 5 войдут в их doInBackground(), остальные 10 попадут в очередь, но 16-го будет создан новый рабочий поток, поэтому он сразу же начнет выполнение. Таким образом, в этом случае не более 6 задач будут выполняться одновременно.

Существует ограничение на количество одновременно запускаемых задач. Поскольку AsyncTaskиспользуется исполнитель пула потоков с ограниченным максимальным числом рабочих потоков (128), а очередь отложенных задач имеет фиксированный размер 10, если вы попытаетесь выполнить более 138 своих пользовательских задач, приложение будет аварийно завершать работу java.util.concurrent.RejectedExecutionException.

Начиная с версии 3.0, API позволяет использовать ваш собственный исполнитель пула потоков через AsyncTask.executeOnExecutor(Executor exec, Params... params)метод. Это позволяет, например, настроить размер очереди отложенных задач, если значение по умолчанию 10 не то, что вам нужно.

Как упоминает @Knossos, есть возможность использовать AsyncTaskCompat.executeParallel(task, params);из библиотеки поддержки v.4 для параллельного запуска задач, не заботясь об уровне API. Этот метод устарел на уровне API 26.0.0.

ОБНОВЛЕНИЕ: 3

Вот простое тестовое приложение для игры с множеством задач, параллельное и параллельное выполнение: https://github.com/vitkhudenko/test_asynctask

ОБНОВЛЕНИЕ: 4 (спасибо @penkzhou за указание на это)

Начиная с Android 4.4 AsyncTaskведет себя иначе, чем было описано в разделе ОБНОВЛЕНИЕ: 2 . Существует исправление, предотвращающее AsyncTaskсоздание слишком большого количества потоков.

До Android 4.4 (API 19) AsyncTaskбыли следующие поля:

private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(10);

В Android 4.4 (API 19) вышеуказанные поля изменены на:

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);

Это изменение увеличивает размер очереди до 128 элементов и уменьшает максимальное количество потоков до количества ядер ЦП * 2 + 1. Приложения могут по-прежнему отправлять такое же количество задач.

Вит Худенко
источник
11
Только к сведению, размер пула ядра равен 5, но максимум равен 128. Это означает, что если вы заполните свою очередь, более 5 (до 128) могут работать одновременно.
Кабуко
2
@kabuko: ты абсолютно прав. Только что проверил это на 2.2 и подтвердил, что если 5 задач уже запущены (размер пула ядра равен 5), то он начинает помещать оставшиеся задачи в очередь отложенных задач, однако, если очередь уже заполнена (максимальный размер очереди равен 10 ), затем запускается дополнительный рабочий поток для задачи, поэтому задача запускается немедленно.
Вит Худенко
1
только что спас мой день, подпись героя :) Спасибо
Кароли
2
Пожалуйста, обновите этот ответ для новых функций . Пример:AsyncTaskCompat.executeParallel(task, params);
Кносс
1
@ Архимед Пожалуйста, обновите ответ, поскольку AsyncTaskCompat.executeParallel(task, params);он устарел
Кирилл Старостин
218

Это позволяет выполнять параллельное выполнение на всех версиях Android с API 4+ (Android 1.6+):

@TargetApi(Build.VERSION_CODES.HONEYCOMB) // API 11
void startMyTask(AsyncTask asyncTask) {
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
        asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    else
        asyncTask.execute(params);
}

Это краткое изложение превосходного ответа Архимеда.

Убедитесь, что вы используете API уровня 11 или выше в качестве цели сборки проекта. В Eclipse это так Project > Properties > Android > Project Build Target. Это не нарушит обратную совместимость с более низкими уровнями API. Не волнуйтесь, вы получите ошибки Lint, если вы случайно используете функции, представленные позже minSdkVersion. Если вы действительно хотите использовать функции, представленные позже minSdkVersion, вы можете устранить эти ошибки с помощью аннотаций, но в этом случае вам нужно позаботиться о совместимости самостоятельно . Это именно то, что произошло во фрагменте кода выше.

sulai
источник
3
Пришлось изменить TimerTask.THREAD_POOL_EXECUTOR на AsynTask.THREAD_POOL_EXECUTOR, тогда этот пример работает
Ronnie
2
Чтобы избежать проблем, вы можете сделать вызов общим: void startMyTask(AsyncTask<Void, ?, ?> asyncTask) (если ваш doInBackground принимает Void)
Peter
Вы можете упростить приведенный выше код еще больше, если ваш класс расширяет AsyncTask <Void, Integer, Void>. Это сработало отлично, спасибо.
Петр Арандоренко
1
Большое спасибо. У меня был сценарий, когда мне нужно было выполнить две асинхронные задачи одновременно для чтения данных с веб-сервера, но я обнаружил, что сервер получает только один URL-адрес, и пока этот конкретный запрос не выполнен, URL-адрес второго AsyncTask не будет попадать на сервер , Ваш ответ решил мою проблему :)
Сантош Гутта
1
Я продолжаю получать голоса за это, спасибо. :) Но у меня сложилось впечатление, что многие люди пытаются использовать AsyncTask для работы в сети, где превосходные библиотеки, такие как Volley , Glide или Swagger, являются лучшим выбором. Обязательно ознакомьтесь с ними перед использованием AsyncTask :)
sulai
21

Сделать предложение @sulai более общим:

@TargetApi(Build.VERSION_CODES.HONEYCOMB) // API 11
public static <T> void executeAsyncTask(AsyncTask<T, ?, ?> asyncTask, T... params) {
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
        asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    else
        asyncTask.execute(params);
}   
AsafK
источник
более общее решение: AsyncTaskCompat.executeParallel (новый MyAsyncTask, myprams);
Викаш Кумар Верма
11

Просто чтобы включить последнее обновление (ОБНОВЛЕНИЕ 4) в безупречный ответ @Arhimed в очень хорошее резюме @sulai:

void doTheTask(AsyncTask task) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // Android 4.4 (API 19) and above
        // Parallel AsyncTasks are possible, with the thread-pool size dependent on device
        // hardware
        task.execute(params);
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { // Android 3.0 to
        // Android 4.3
        // Parallel AsyncTasks are not possible unless using executeOnExecutor
        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    } else { // Below Android 3.0
        // Parallel AsyncTasks are possible, with fixed thread-pool size
        task.execute(params);
    }
}
Али Нем
источник
Ух ты, классно. Это означает, .executeOnExecutor()что теперь избыточно для API> = KITKAT, да?
Таслим Осени
4

Пример загрузки растровых изображений разработчиками Android эффективно использует пользовательскую асинхронную задачу (скопированную с jellybean), поэтому вы можете использовать executeOnExecutor с значением apis ниже <11

http://developer.android.com/training/displaying-bitmaps/index.html

Загрузите код и перейдите в пакет утилит.

OriolJ
источник
3

Это возможно. Версия моего Android-устройства 4.0.4 и android.os.Build.VERSION.SDK_INT - 15

У меня 3 блесны

Spinner c_fruit=(Spinner) findViewById(R.id.fruits);
Spinner c_vegetable=(Spinner) findViewById(R.id.vegetables);
Spinner c_beverage=(Spinner) findViewById(R.id.beverages);

А также у меня есть класс Async-Tack.

Вот мой код загрузки блесны

RequestSend reqs_fruit = new RequestSend(this);
reqs_fruit.where="Get_fruit_List";
reqs_fruit.title="Loading fruit";
reqs_fruit.execute();

RequestSend reqs_vegetable = new RequestSend(this);
reqs_vegetable.where="Get_vegetable_List";
reqs_vegetable.title="Loading vegetable";
reqs_vegetable.execute();

RequestSend reqs_beverage = new RequestSend(this);
reqs_beverage.where="Get_beverage_List";
reqs_beverage.title="Loading beverage";
reqs_beverage.execute();

Это работает отлично. Один за другим загружены мои счетчики. Я не пользователь executeOnExecutor.

Вот мой класс Async-задачи

public class RequestSend  extends AsyncTask<String, String, String > {

    private ProgressDialog dialog = null;
    public Spinner spin;
    public String where;
    public String title;
    Context con;
    Activity activity;      
    String[] items;

    public RequestSend(Context activityContext) {
        con = activityContext;
        dialog = new ProgressDialog(activityContext);
        this.activity = activityContext;
    }

    @Override
    protected void onPostExecute(String result) {
        try {
            ArrayAdapter<String> adapter = new ArrayAdapter<String> (activity, android.R.layout.simple_spinner_item, items);       
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            spin.setAdapter(adapter);
        } catch (NullPointerException e) {
            Toast.makeText(activity, "Can not load list. Check your connection", Toast.LENGTH_LONG).show();
            e.printStackTrace();
        } catch (Exception e)  {
            Toast.makeText(activity, "Can not load list. Check your connection", Toast.LENGTH_LONG).show();
            e.printStackTrace();
        }
        super.onPostExecute(result);

        if (dialog != null)
            dialog.dismiss();   
    }

    protected void onPreExecute() {
        super.onPreExecute();
        dialog.setTitle(title);
        dialog.setMessage("Wait...");
        dialog.setCancelable(false); 
        dialog.show();
    }

    @Override
    protected String doInBackground(String... Strings) {
        try {
            Send_Request();
            } catch (NullPointerException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        return null;
    }

    public void Send_Request() throws JSONException {

        try {
            String DataSendingTo = "http://www.example.com/AppRequest/" + where;
            //HttpClient
            HttpClient httpClient = new DefaultHttpClient();
            //Post header
            HttpPost httpPost = new HttpPost(DataSendingTo);
            //Adding data
            List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);

            nameValuePairs.add(new BasicNameValuePair("authorized","001"));

            httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
            // execute HTTP post request
            HttpResponse response = httpClient.execute(httpPost);

            BufferedReader reader;
            try {
                reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
                StringBuilder builder = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null) {
                    builder.append(line) ;
                }

                JSONTokener tokener = new JSONTokener(builder.toString());
                JSONArray finalResult = new JSONArray(tokener);
                items = new String[finalResult.length()]; 
                // looping through All details and store in public String array
                for(int i = 0; i < finalResult.length(); i++) {
                    JSONObject c = finalResult.getJSONObject(i);
                    items[i]=c.getString("data_name");
                }

            } catch (ClientProtocolException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Саджита Ратнаяке
источник
2
как ты вообще видишь, что они не выполняются последовательно?
behelit
@behelit Имеет ли значение, когда он работает? Пожалуйста, прочитайте Принятый ответ для более подробной информации stackoverflow.com/a/4072832/2345900
Саджита Ратнаяке
2
Извините, я не понимаю. Если вы не можете сказать, являются ли ваши асинхронные последовательными или действительно асинхронными, тогда это имеет значение. Кроме того, в связанном ответе говорится об использовании метода executeonexecutor, который вы на самом деле не используете в своем ответе. Извините, если я что-то пропустил.
behelit
1
«Один за другим загружены мои счетчики». Вы буквально сами сказали, что это последовательное выполнение, а не параллельное выполнение. Каждая из наших AsyncTasks добавляется в очередь и обрабатывается последовательно. Это не отвечает на вопрос ОП.
Джошуа Пинтер
Это очень старый ответ. Если это не работает для вас, пожалуйста, обратитесь к принятому ответу
Sajitha Rathnayake
3

если вы хотите выполнять задачи параллельно, вам нужно вызвать метод executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "your task name")после Android версии 3.0; но этот метод не существует до Android 3.0 и после 1.6, потому что он выполняется параллельно сам по себе, поэтому я предлагаю вам настроить свой собственный класс AsyncTask в вашем проекте, чтобы избежать исключения исключения в другой версии Android.

альфа
источник