Запуск декількох 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. Гммм ...


Ваш код працює для мене, тому проблема повинна бути десь в іншому місці. Ви ввели фільтр у своєму перегляді 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 можуть працювати одночасно. На жаль, я не пам’ятаю, в якій версії вони саме це змінили.

ОНОВЛЕННЯ:

Ось що про це говорить поточний (2012-01-27) API:

При першому введенні AsyncTasks виконувались послідовно на одному фоновому потоці. Починаючи з DONUT, це було змінено на пул потоків, що дозволяють паралельно працювати безлічі завдань. Після HONEYCOMB планується змінити цю функцію на один потік, щоб уникнути поширених помилок програми, викликаних паралельним виконанням. Якщо ви справді хочете паралельного виконання, ви можете використовувати версію цього методу ExecuteOnExecutor (Виконавець, Параметри ...) з 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поводиться інакше, ніж описано в розділі UPDATE: 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 елементів та зменшує максимальну кількість потоків до кількості ядер CPU * 2 + 1. Програми все ще можуть подавати однакову кількість завдань.


11
Просто FYI, розмір основного пулу становить 5, але макс - 128. Це означає, що якщо ви заповнили свою чергу, більше 5 (до 128) можуть працювати одночасно.
кабуко

2
@kabuko: ти абсолютно правий. Щойно досліджуйте це на 2.2 і підтверджуючи, що якщо 5 завдань вже запущені (розмір основного пулу - 5), то він починає ставити решту завдань у чергу відкладених завдань, однак якщо черга вже заповнена (максимальний розмір черги - 10 ), тоді він запускає додатковий робочий потік для завдання, тому завдання починається негайно.
Віт Худенко

1
щойно врятував мій день, герой підпису :) Дякую
Каролі

2
Оновіть цю відповідь для нових функцій compat . Приклад:AsyncTaskCompat.executeParallel(task, params);
Knossos

1
@Arhimed Будь ласка, оновіть відповідь, оскільки 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, ви можете придушити ці помилки за допомогою приміток, але в цьому випадку вам потрібно подбати про сумісність самостійно . Саме це і сталося в фрагменті коду вище.


3
Довелося змінити TimerTask.THREAD_POOL_EXECUTOR на AsynTask.THREAD_POOL_EXECUTOR, тоді цей приклад працює
Ronnie

2
Щоб скасувати проблеми, ви можете зробити дзвінок загальним: void startMyTask(AsyncTask<Void, ?, ?> asyncTask) (якщо ваш doInBackground недійсний)
Пітер

Ви можете спростити вищезазначений код ще більше, якщо ваш клас поширює AsyncTask <Void, Integer, Void>. Це спрацювало чудовий сулай, спасибі.
Петро Арандоренко

1
Дуже дякую. У мене був сценарій, коли мені потрібно виконати два завдання асинхронізації одночасно, щоб прочитати дані з веб-сервера, але я виявив, що сервер отримує лише одну URL-адресу і доки цей конкретний запит не буде виконаний, другий URL-адресу AsyncTask не вдарить на сервер . Ваша відповідь вирішила мою проблему :)
Santhosh Gutta

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

більш загальне рішення: 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

Завантажте код та перейдіть до пакету util.


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
Sajitha Rathnayake

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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.