Uruchamianie wielu AsyncTasks jednocześnie - niemożliwe?

259

Próbuję uruchomić dwa AsyncTasks jednocześnie. (Platforma to Android 1.5, HTC Hero.) Jednak uruchamiany jest tylko pierwszy. 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.");
    }
}

Oczekiwany wynik to:

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

I tak dalej. Dostaję jednak:

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

Drugi AsyncTask nigdy nie zostanie wykonany. Jeśli zmienię kolejność instrukcji execute (), tylko zadanie foo wygeneruje dane wyjściowe.

Czy brakuje mi czegoś oczywistego i / lub robię coś głupiego? Czy nie jest możliwe jednoczesne uruchomienie dwóch AsyncTasks?

Edycja: Zdałem sobie sprawę, że dany telefon działa pod kontrolą Androida 1.5, zaktualizowałem opis problemu. odpowiednio. Nie mam tego problemu z telefonem HTC Hero z systemem Android 2.1. Hmmm ...

rodion
źródło
Twój kod działa dla mnie, więc problem musi być gdzie indziej. Czy wprowadziłeś filtr w widoku LogCat? ;-)
mreichelt
Hm, to dziwne. Nie mam żadnego filtrowania w logcat. Czy używasz również wersji 1.6? Jeśli tak, to który telefon?
rodion
Ups, właśnie zdałem sobie sprawę, że działa (starożytny) Android 1.5
rodion
1
Użyłem Androida 1.6 jako celu i emulatora Androida 2.1. Więc jeśli problem naprawdę występuje na telefonie HTC Hero tylko z Androidem 1.5 - wkręć je, nic ci nie jest. ;-) HTC Hero ma już aktualizację do nowszej wersji Androida. Nie zawracałbym sobie tym głowy, jeśli są producenci, którzy coś psują. Ponadto nie miałbym już nic przeciwko Androidowi 1.5.
mreichelt
AsyncTask należy stosować do zadań o krótszym czasie trwania 5 ms. Przejdź do ThreadPoolExecutor ( developer.android.com/reference/java/util/concurrent/… ). Powiązany post: stackoverflow.com/questions/6964011/…
Ravindra babu

Odpowiedzi:

438

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

AKTUALIZACJA:

Oto, co mówi na ten temat bieżący (2012-01-27) interfejs API:

Po raz pierwszy AsyncTasks były wykonywane szeregowo w jednym wątku w tle. Począwszy od DONUT, zmieniono to na pulę wątków, umożliwiając równoległe wykonywanie wielu zadań. Po HONEYCOMB planuje się zmienić to z powrotem na pojedynczy wątek, aby uniknąć typowych błędów aplikacji spowodowanych równoległym wykonywaniem. Jeśli naprawdę chcesz wykonywać równolegle, możesz użyć wersji tej metody executeOnExecutor (Executor, Params ...) z THREAD_POOL_EXECUTOR; jednak zobacz komentarz tam ostrzeżenia o jego użyciu.

DONUT to Android 1.6, HONEYCOMB to Android 3.0.

AKTUALIZACJA: 2

Zobacz komentarz kabukood Mar 7 2012 at 1:27.

Okazuje się, że w przypadku interfejsów API, w których używana jest „pula wątków umożliwiająca równoległe działanie wielu zadań” (od wersji 1.6 do wersji 3.0) liczba jednoczesnych uruchomień AsyncTasks zależy od liczby zadań, które zostały już przekazane do wykonania, ale jeszcze nie skończyli doInBackground().

To zostało przetestowane / potwierdzone przeze mnie w wersji 2.2. Załóżmy, że masz niestandardową funkcję AsyncTask, która tylko śpi sekundę doInBackground(). AsyncTasks używają wewnętrznie kolejki o stałym rozmiarze do przechowywania opóźnionych zadań. Rozmiar kolejki to domyślnie 10. Jeśli rozpoczniesz 15 niestandardowych zadań z rzędu, pierwsze 5 wprowadzi je doInBackground(), ale reszta będzie czekać w kolejce na wolny wątek roboczy. Gdy tylko jedna z pierwszych 5 zakończy się, a tym samym zwolni wątek roboczy, zadanie z kolejki rozpocznie wykonywanie. Tak więc w tym przypadku jednocześnie uruchomi się maksymalnie 5 zadań. Jeśli jednak uruchomisz 16 niestandardowych zadań z rzędu, pierwsze 5 wprowadzi je doInBackground(), a pozostałe 10 dostanie się do kolejki, ale dla 16. zostanie utworzony nowy wątek roboczy, aby natychmiast rozpocząć wykonywanie. Tak więc w tym przypadku jednocześnie uruchomi się maksymalnie 6 zadań.

Istnieje limit liczby zadań, które można uruchomić jednocześnie. Ponieważ AsyncTaskużywa modułu wykonującego pulę wątków z ograniczoną maksymalną liczbą wątków roboczych (128), a kolejka zadań opóźnionych ma ustalony rozmiar 10, próba wykonania więcej niż 138 niestandardowych zadań spowoduje awarię aplikacji java.util.concurrent.RejectedExecutionException.

Począwszy od wersji 3.0 interfejs API pozwala na użycie niestandardowego modułu wykonującego pulę wątków za pomocą AsyncTask.executeOnExecutor(Executor exec, Params... params)metody. Pozwala to na przykład skonfigurować rozmiar kolejki opóźnionych zadań, jeśli domyślna wartość 10 nie jest potrzebna.

Jak wspomina @Knossos, istnieje możliwość korzystania AsyncTaskCompat.executeParallel(task, params);z biblioteki wsparcia v.4 do równoległego uruchamiania zadań bez zawracania sobie głowy poziomem API. Ta metoda stała się przestarzała na poziomie interfejsu API 26.0.0.

AKTUALIZACJA: 3

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

AKTUALIZACJA: 4 (dzięki @penkzhou za zwrócenie na to uwagi)

Począwszy od Androida 4.4 AsyncTaskzachowuje się inaczej niż opisano w sekcji AKTUALIZACJA: 2 . Istnieje poprawka zapobiegająca AsyncTasktworzeniu zbyt wielu wątków.

Przed Androidem 4.4 (API 19) AsyncTaskmiał 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 zostały zmienione na:

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 elementów i zmniejsza maksymalną liczbę wątków do liczby rdzeni procesora * 2 + 1. Aplikacje mogą nadal przesyłać tę samą liczbę zadań.

Vit Khudenko
źródło
11
Po prostu FYI, wielkość puli podstawowej wynosi 5, ale maksymalna to 128. Oznacza to, że jeśli zapełnisz kolejkę, więcej niż 5 (do 128) może działać jednocześnie.
kabuko
2
@kabuko: masz absolutną rację. Właśnie to zbadałem w 2.2 i potwierdzając, że jeśli 5 zadań już działa (wielkość puli podstawowej wynosi 5), wówczas zaczyna umieszczać pozostałe zadania w kolejce zadań opóźnionych, jednak jeśli kolejka jest już pełna (maksymalny rozmiar kolejki wynosi 10 ), a następnie uruchamia dodatkowy wątek roboczy dla zadania, więc zadanie uruchamia się natychmiast.
Vit Khudenko,
1
właśnie uratowałem mój dzień, Bohater podpisu :) Dziękuję
Karoly
2
Zaktualizuj tę odpowiedź dla nowych funkcji kompatybilności . Przykład:AsyncTaskCompat.executeParallel(task, params);
Knossos,
1
@Arhimed Proszę zaktualizuj odpowiedź, ponieważ AsyncTaskCompat.executeParallel(task, params);jest już przestarzała
Kirill Starostin
218

Pozwala to na równoległe wykonywanie na 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 streszczenie doskonałej odpowiedzi Arhimed.

Upewnij się, że używasz interfejsu API poziomu 11 lub wyższego jako celu kompilacji projektu. To znaczy w Eclipse Project > Properties > Android > Project Build Target. Nie wpłynie to na zgodność wsteczną na niższe poziomy API. Nie martw się, otrzymasz błędy Lint, jeśli przypadkowo użyjesz funkcji wprowadzonych później minSdkVersion. Jeśli naprawdę chcesz korzystać z funkcji wprowadzonych później niż minSdkVersionmożna tłumić te błędy za pomocą adnotacji, ale w tym przypadku trzeba zadbać o zgodność siebie . Dokładnie tak się stało w powyższym fragmencie kodu.

sulai
źródło
3
Musiałem zmienić TimerTask.THREAD_POOL_EXECUTOR na AsynTask.THREAD_POOL_EXECUTOR, to ten przykład działa
Ronnie
2
Aby anulować problemy, możesz void startMyTask(AsyncTask<Void, ?, ?> asyncTask) ustawić ogólne wywołanie: (jeśli Twój doInBackground przyjmuje Void)
Peter
Możesz uprościć powyższy kod jeszcze bardziej, jeśli twoja klasa rozszerza AsyncTask <Void, Integer, Void>. Działa świetnie, dzięki.
Peter Arandorenko
1
Wielkie dzięki. Miałem scenariusz, w którym muszę wykonać dwa zadania asynchroniczne jednocześnie, aby odczytać dane z serwera WWW, ale znalazłem, że serwer otrzymuje tylko jeden adres URL i dopóki to konkretne żądanie nie zostanie zakończone, drugi adres URL AsyncTask nie trafi na serwer . Twoja odpowiedź rozwiązała mój problem :)
Santhosh Gutta
1
Ciągle otrzymuję za to głosy uznania, dziękuję. :) Ale mam wrażenie, że wiele osób próbuje używać AsyncTask do tworzenia sieci, gdzie doskonałe biblioteki, takie jak Volley , Glide czy Swagger, są lepszym wyborem. Sprawdź je przed użyciem AsyncTask :)
sulai,
21

Bardziej ogólne sugestie @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
źródło
bardziej ogólne rozwiązanie: AsyncTaskCompat.executeParallel (new MyAsyncTask, myprams);
Vikash Kumar Verma
11

Wystarczy dołączyć najnowszą aktualizację (UPDATE 4) do nieskazitelnej odpowiedzi @Arhimed w bardzo dobrym streszczeniu @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);
    }
}
Ali Nem
źródło
Och, wow, super. Oznacza to, że .executeOnExecutor()jest teraz redundantny dla API> = KITKAT, tak?
Taslim Oseni
3

Jest to możliwe. Moja wersja urządzenia z Androidem to 4.0.4, a android.os.Build.VERSION.SDK_INT to 15

Mam 3 błystki

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

A także mam klasę Async-Tack.

Oto mój kod ładowania tarczy

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 błystki załadowane. Nie wykonałem użytkownika executeOnExecutor.

Oto moja klasa zadań asynchronicznych

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();
        }
    }
}
Sajitha Rathnayake
źródło
2
jak widzisz, że nie są one wykonywane sekwencyjnie?
behelit
@behelit Czy to ważne, kiedy działa? Proszę przeczytać Zaakceptowaną odpowiedź, aby uzyskać więcej informacji stackoverflow.com/a/4072832/2345900
Sajitha Rathnayake
2
Przepraszam nie rozumiem Jeśli nie możesz stwierdzić, czy asynchroniczne są sekwencyjne, czy naprawdę asynchroniczne, ma to znaczenie. Połączona odpowiedź mówi również o użyciu metody executeonexecutor, której tak naprawdę nie używasz w swojej odpowiedzi. Przepraszam, jeśli coś przeoczyłem.
behelit
1
„Jeden po drugim moje błystki załadowane”. Dosłownie powiedziałeś, że jest to wykonanie seryjne, a nie równoległe. Każde z naszych zadań AsyncTask jest dodawane do kolejki i przetwarzane sekwencyjnie. To nie odpowiada na pytanie PO.
Joshua Pinter
To bardzo stara odpowiedź. Jeśli to nie zadziała, zapoznaj się z przyjętą odpowiedzią
Sajitha Rathnayake
3

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 wersji 1.6, ponieważ działa ona równolegle sama, więc sugeruję, abyś dostosował własną klasę AsyncTask w swoim projekcie, aby uniknąć zgłaszania wyjątku w innej wersji Androida.

alfa
źródło