Uruchamianie wielu asynchronicznych zadań w tym samym czasie - nie jest możliwe?

Próbuję uruchomić dwie asynchroniczne zadania w tym samym czasie. (Platforma to Android 1.5, HTC Hero.) Jednak tylko pierwszy zostaje stracony. Oto prosty fragment opisujący mój problem:

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

Wyjście, którego oczekuję to:

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

I tak dalej. Jednak to co dostaję to:

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

Druga AsyncTask nigdy nie zostanie wykonana. Jeśli zmienię kolejność poleceń execute (), tylko zadanie foo wygeneruje wyjście.

Czy brakuje mi tu czegoś oczywistego i / lub robienia czegoś głupi? Czy nie można uruchomić dwóch asynchronicznych zadań w tym samym czasie?

Edit: zdałem sobie sprawę, że dany telefon działa z Androidem 1.5, zaktualizowałem problem descr. odpowiednio. Nie mam tego problemu z HTC Hero z Androidem 2.1. Hmmm ...

Author: Ravindra babu, 2010-11-01

7 answers

AsyncTask używa wzorca puli wątków do uruchamiania rzeczy z doinbackground (). Problem polega na tym, że początkowo (we wczesnych wersjach systemu Android) rozmiar puli wynosił tylko 1, co oznacza brak równoległych obliczeń dla kilku asynchronicznych Zadań. Ale później to naprawili i teraz rozmiar wynosi 5, więc co najwyżej 5 asynchronicznych zadań może działać jednocześnie. Niestety nie pamiętam w jakiej dokładnie wersji to zmienili.

UPDATE:

Oto co mówi obecny (2012-01-27) API na to:

Kiedy po raz pierwszy wprowadzono, asynchroniczne zadania były wykonywane seryjnie na jednym wątku tła. Począwszy od pączka, została ona zmieniona na pulę wątków pozwalającą na równoległe działanie wielu zadań. Po wykonaniu HONEYCOMB planowana jest zmiana tego wątku z powrotem na pojedynczy wątek, aby uniknąć typowych błędów aplikacji spowodowanych wykonaniem równoległym. Jeśli naprawdę chcesz wykonywać równoległe, możesz użyć wykonaniaexecutor( Executor, Params...) wersja tej metody z THREAD_POOL_EXECUTOR; jednak, patrz komentarz tam ostrzeżenia o jego użyciu.

DONUT to Android 1.6, HONEYCOMB to Android 3.0.

Aktualizacja: 2

Zobacz komentarz kabuko z Mar 7 at 1:27.

Okazuje się, że dla API, w których używana jest "pula wątków pozwalająca na równoległe działanie wielu zadań" (począwszy od 1.6, a skończywszy na 3.0), liczba uruchomionych jednocześnie asynchronicznych zadań zależy od tego, ile zadań zostało przekazanych do wykonania już, ale jeszcze nie skończyli doInBackground().

To jest testowane/potwierdzone przeze mnie na 2.2. Załóżmy, że masz niestandardową AsyncTask, która śpi przez sekundę w doInBackground(). Asynchroniczne zadania używają kolejki o stałym rozmiarze do przechowywania opóźnionych zadań. Domyślnie rozmiar kolejki wynosi 10. Jeśli uruchomisz 15 niestandardowych zadań z rzędu, pierwsze 5 wprowadzi ich doInBackground(), ale reszta będzie czekać w kolejce do darmowego wątku roboczego. Jak tylko któraś z pierwszych 5 zakończy, a tym samym zwolni wątek roboczy, zadanie z kolejki rozpocznie się wykonanie. Tak więc w tym przypadku co najwyżej 5 zadań będzie działać jednocześnie. Jeśli jednak uruchomisz 16 swoich niestandardowych zadań z rzędu, to pierwsze 5 wprowadzi ich doInBackground(), pozostałe 10 trafi do kolejki, ale dla 16. zostanie utworzony nowy wątek roboczy, aby natychmiast rozpocząć wykonywanie. Tak więc w tym przypadku co najwyżej 6 zadań będzie działać jednocześnie.

Istnieje limit liczby zadań, które mogą być uruchamiane jednocześnie. Ponieważ AsyncTask używa executora puli wątków z ograniczoną Maksymalna liczba wątków roboczych (128) i Kolejka opóźnionych zadań ma stały rozmiar 10, jeśli spróbujesz wykonać więcej niż 138 niestandardowych zadań, aplikacja zawiesi się z java.util.concurrent.RejectedExecutionException.

Począwszy od wersji 3.0 API pozwala na korzystanie z niestandardowego executora puli wątków za pomocą metody AsyncTask.executeOnExecutor(Executor exec, Params... params). Pozwala to na przykład skonfigurować rozmiar opóźnionej kolejki zadań, jeśli domyślna 10 nie jest tym, czego potrzebujesz.

Jak wspomina @Knossos, istnieje opcja użycia AsyncTaskCompat.executeParallel(task, params); z biblioteki support V. 4 do uruchamiania zadań w równolegle bez zawracania sobie głowy poziomem API. Ta metoda stała się przestarzała w API na poziomie 26.0.0.

Aktualizacja: 3

Oto prosta aplikacja testowa do zabawy z wieloma zadaniami, wykonanie szeregowe vs. równoległe: https://github.com/vitkhudenko/test_asynctask

UPDATE: 4 (Dzięki @ penkzhou za wskazanie tego)

Począwszy od Androida 4.4 AsyncTask zachowuje się inaczej niż to, co opisano w sekcji UPDATE: 2. Tam jest poprawką , aby zapobiec tworzeniu zbyt wielu wątków.

Przed Androidem 4.4 (API 19) AsyncTask miał następujące pola:

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

W Androidzie 4.4 (API 19) powyższe pola są zmieniane na to:

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

Ta zmiana zwiększa rozmiar kolejki do 128 pozycji i zmniejsza maksymalną liczbę wątków do liczby rdzeni procesora * 2 + 1. Aplikacje mogą nadal wysyłać tę samą liczbę zadań.

 410
Author: Vit Khudenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-19 19:14:11

Pozwala to na równoległe wykonywanie we wszystkich wersjach Androida z 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);
}
To jest podsumowanie doskonałej odpowiedzi Arhimeda.

Upewnij się, że używasz API poziomu 11 lub wyższego jako celu budowania projektu. W Eclipse, czyli Project > Properties > Android > Project Build Target. spowoduje to , a nie złamanie kompatybilności wstecznej do niższych poziomów API. nie martw się, pojawią się błędy Lint, jeśli przypadkowo użyjesz funkcji wprowadzonych później niż minSdkVersion. Jeśli naprawdę chcesz użyć funkcje wprowadzone później niż minSdkVersion, można wyeliminować te błędy za pomocą adnotacji, ale w takim przypadku należy zadbać o kompatybilność samodzielnie . Dokładnie tak było w powyższym fragmencie kodu.

 200
Author: sulai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-14 10:45:35

Tworzenie sugestii @ sulai bardziej ogólnych:

@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);
}   
 20
Author: AsafK,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-30 13:23:45

Aby dodać najnowszą aktualizację (UPDATE 4) w nieskazitelnej odpowiedzi @Arhimed w bardzo dobrym podsumowaniu @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);
    }
}
 6
Author: Ali Nem,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-27 03:35:06

Programiści Androida przykład ładowania bitmap efektywnie wykorzystuje niestandardową asynctask (skopiowaną z jellybean), dzięki czemu można używać executeOnExecutor w API niższych niż

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

Pobierz kod i przejdź do pakietu util.

 4
Author: OriolJ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-22 10:12:25

Jest to możliwe. Moja wersja urządzenia z Androidem to 4.0.4 i android.os.Buduj.Wersja.SDK_INT to 15

Mam 3 Spinnery

Spinner c_fruit=(Spinner) findViewById(R.id.fruits);
Spinner c_vegetable=(Spinner) findViewById(R.id.vegetables);
Spinner c_beverage=(Spinner) findViewById(R.id.beverages);
Mam też zajęcia asynchroniczne.

Oto Mój kod ładowania spinnera

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();
To działa idealnie. Jeden po drugim moje Spinnery ładowane. Nie zrobiłem user executeOnExecutor.

Oto moja klasa Async-task

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();
        }
    }
}
 3
Author: Sajitha Nilan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-31 06:27:39

Jeśli chcesz wykonywać zadania równolegle, musisz wywołać metodę executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "your task name") po wersji Androida 3.0; ale ta metoda nie istnieje przed Androidem 3.0 i po 1.6, ponieważ sama wykonuje równolegle, więc proponuję dostosować własną klasę AsyncTask w projekcie, aby uniknąć wyjątku throw w innej wersji Androida.

 1
Author: alpha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-24 12:29:39