Menjalankan beberapa AsyncTasks secara bersamaan - tidak mungkin?

259

Saya mencoba menjalankan dua AsyncTasks secara bersamaan. (Platform adalah Android 1.5, HTC Hero.) Namun, hanya yang pertama yang dieksekusi. Berikut cuplikan sederhana untuk menjelaskan masalah saya:

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

Output yang saya harapkan adalah:

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

Dan seterusnya. Namun, yang saya dapatkan adalah:

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

AsyncTask kedua tidak pernah dieksekusi. Jika saya mengubah urutan pernyataan execut (), hanya tugas foo yang akan menghasilkan output.

Apakah saya kehilangan sesuatu yang jelas di sini dan / atau melakukan sesuatu yang bodoh? Apakah tidak mungkin menjalankan dua AsyncTasks secara bersamaan?

Sunting: Saya menyadari telepon yang dipermasalahkan menjalankan Android 1.5, saya memperbarui masalah descr. demikian. Saya tidak punya masalah dengan HTC Hero yang menjalankan Android 2.1. Hmmm ...

rodion
sumber
Kode Anda berfungsi untuk saya, jadi masalahnya harus ada di tempat lain. Apakah Anda memasukkan filter dalam tampilan LogCat Anda? ;-)
mreichelt
Hm, itu aneh. Saya tidak punya penyaringan di logcat. Apakah Anda menggunakan 1,6 juga? Jika ya, telepon yang mana?
rodion
Ups, baru sadar itu menjalankan Android 1.5 (kuno)
rodion
1
Saya menggunakan Android 1.6 sebagai target dan emulator Android 2.1. Jadi jika masalah benar-benar terjadi pada HTC Hero dengan Android 1.5 saja - persetan, Anda baik-baik saja. ;-) HTC Hero sudah memiliki pembaruan ke versi Android yang lebih baru. Saya tidak akan repot tentang itu jika ada beberapa produsen yang mengacaukan semuanya. Selain itu, saya tidak akan keberatan dengan Android 1.5 lagi.
mreichelt
AsyncTask harus digunakan untuk tugas dengan durasi lebih pendek, yaitu 5 ms. Pindah ke ThreadPoolExecutor ( developer.android.com/reference/java/util/concurrent/… ). Posting terkait: stackoverflow.com/questions/6964011/…
Ravindra babu

Jawaban:

438

AsyncTask menggunakan pola kumpulan thread untuk menjalankan hal-hal dari doInBackground (). Masalahnya awalnya (dalam versi OS Android awal) ukuran kolam hanya 1, yang berarti tidak ada perhitungan paralel untuk sekelompok AsyncTasks. Tetapi kemudian mereka memperbaikinya dan sekarang ukurannya 5, sehingga paling banyak 5 AsyncTasks dapat berjalan secara bersamaan. Sayangnya saya tidak ingat dalam versi apa tepatnya mereka mengubahnya.

MEMPERBARUI:

Inilah yang dikatakan API (2012-01-27) terkini tentang ini:

Ketika pertama kali diperkenalkan, AsyncTasks dieksekusi secara serial pada utas latar belakang tunggal. Dimulai dengan DONUT, ini diubah menjadi kumpulan untaian yang memungkinkan beberapa tugas beroperasi secara paralel. Setelah HONEYCOMB, direncanakan untuk mengubah ini kembali ke utas tunggal untuk menghindari kesalahan aplikasi umum yang disebabkan oleh eksekusi paralel. Jika Anda benar-benar ingin eksekusi paralel, Anda dapat menggunakan versi executeOnExecutor (Executor, Params ...) dari metode ini dengan THREAD_POOL_EXECUTOR; Namun, lihat komentar di sana untuk peringatan tentang penggunaannya.

DONUT adalah Android 1.6, HONEYCOMB adalah Android 3.0.

PEMBARUAN: 2

Lihat komentar dari kabukodari Mar 7 2012 at 1:27.

Ternyata untuk API di mana "kumpulan thread yang memungkinkan beberapa tugas untuk beroperasi secara paralel" digunakan (mulai dari 1,6 dan berakhir pada 3,0) jumlah menjalankan AsyncTasks secara bersamaan tergantung pada berapa banyak tugas yang telah dilewati untuk dieksekusi, tetapi belum selesai doInBackground().

Ini diuji / dikonfirmasi oleh saya di 2.2. Misalkan Anda memiliki AsyncTask khusus yang hanya memerlukan waktu satu detik doInBackground(). AsyncTasks menggunakan antrian ukuran tetap secara internal untuk menyimpan tugas yang tertunda. Ukuran antrian adalah 10 secara default. Jika Anda memulai 15 tugas kustom Anda secara berturut-turut, maka 5 tugas pertama akan masuk doInBackground(), tetapi sisanya akan menunggu dalam antrian untuk utas pekerja gratis. Segera setelah salah satu dari 5 yang pertama selesai, dan dengan demikian melepaskan utas pekerja, tugas dari antrian akan memulai eksekusi. Jadi dalam hal ini paling banyak 5 tugas akan berjalan secara bersamaan. Namun jika Anda memulai 16 tugas kustom Anda secara berturut-turut, maka 5 pertama akan memasukkannya doInBackground(), sisanya 10 akan masuk ke dalam antrian, tetapi untuk ke-16 utas pekerja baru akan dibuat sehingga akan segera memulai eksekusi. Jadi dalam hal ini paling banyak 6 tugas akan berjalan secara bersamaan.

Ada batasan berapa banyak tugas yang dapat dijalankan secara bersamaan. Karena AsyncTaskmenggunakan pelaksana kumpulan thread dengan jumlah maksimal utas pekerja (128) dan antrian tugas yang tertunda memiliki ukuran 10, jika Anda mencoba menjalankan lebih dari 138 tugas khusus, aplikasi akan macet java.util.concurrent.RejectedExecutionException.

Mulai dari 3.0, API memungkinkan untuk menggunakan pelaksana kumpulan thread khusus Anda melalui AsyncTask.executeOnExecutor(Executor exec, Params... params)metode. Ini memungkinkan, misalnya, untuk mengkonfigurasi ukuran antrian tugas yang tertunda jika default 10 bukan yang Anda butuhkan.

Seperti @Knossos menyebutkan, ada opsi untuk digunakan AsyncTaskCompat.executeParallel(task, params);dari pustaka dukungan v.4 untuk menjalankan tugas secara paralel tanpa repot dengan level API. Metode ini menjadi usang di API level 26.0.0.

PEMBARUAN: 3

Berikut ini adalah aplikasi pengujian sederhana untuk bermain dengan sejumlah tugas, eksekusi serial vs paralel: https://github.com/vitkhudenko/test_asynctask

UPDATE: 4 (terima kasih @penkzhou untuk menunjukkan ini)

Mulai dari Android 4.4 AsyncTaskberperilaku berbeda dari apa yang dijelaskan dalam bagian UPDATE: 2 . Ada perbaikan untuk mencegah AsyncTaskmembuat terlalu banyak utas.

Sebelum Android 4.4 (API 19) AsyncTaskmemiliki bidang-bidang berikut:

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

Di Android 4.4 (API 19) bidang di atas diubah menjadi ini:

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

Perubahan ini meningkatkan ukuran antrian menjadi 128 item dan mengurangi jumlah utas maksimum menjadi jumlah inti CPU * 2 + 1. Aplikasi masih dapat mengirimkan jumlah tugas yang sama.

Vit Khudenko
sumber
11
Hanya FYI, ukuran inti pool adalah 5, tetapi maks adalah 128. Ini berarti bahwa jika Anda mengisi antrian Anda, lebih dari 5 (hingga 128) dapat berjalan secara bersamaan.
kabuko
2
@ Kabuko: Anda memang benar. Baru saja menyelidiki ini pada 2.2 dan mengonfirmasi bahwa jika 5 tugas sudah berjalan (ukuran inti kolam adalah 5), maka itu mulai menempatkan sisa tugas ke dalam antrian tugas yang tertunda, namun jika antrian sudah penuh (ukuran antrian maks adalah 10) ), lalu memulai utas pekerja tambahan untuk tugas tersebut sehingga tugas dimulai dengan segera.
Vit Khudenko
1
baru saja menyelamatkan hari saya, pahlawan gambar :) Terima kasih
Karoly
2
Harap perbarui jawaban ini untuk fungsi compat yang baru . Contoh:AsyncTaskCompat.executeParallel(task, params);
Knossos
1
@Arhimed Tolong, perbarui jawaban karena AsyncTaskCompat.executeParallel(task, params);sekarang sudah tidak digunakan lagi
Kirill Starostin
218

Ini memungkinkan eksekusi paralel pada semua versi Android dengan 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);
}

Ini ringkasan jawaban Arhimed yang luar biasa.

Pastikan Anda menggunakan API level 11 atau lebih tinggi sebagai target build proyek Anda. Dalam Eclipse, itu Project > Properties > Android > Project Build Target. Ini tidak akan merusak kompatibilitas ke belakang ke tingkat API yang lebih rendah. Jangan khawatir, Anda akan mendapatkan kesalahan Lint jika Anda secara tidak sengaja menggunakan fitur yang diperkenalkan paling lambat minSdkVersion. Jika Anda benar-benar ingin menggunakan fitur diperkenalkan lambat minSdkVersion, Anda dapat menekan kesalahan-kesalahan menggunakan penjelasan, tetapi dalam hal ini, Anda perlu berhati-hati tentang kompatibilitas sendiri . Inilah yang terjadi pada cuplikan kode di atas.

sulai
sumber
3
Harus mengubah TimerTask.THREAD_POOL_EXECUTOR menjadi AsynTask.THREAD_POOL_EXECUTOR maka contoh ini berfungsi
Ronnie
2
Untuk membatalkan masalah, Anda dapat membuat panggilan umum: void startMyTask(AsyncTask<Void, ?, ?> asyncTask) (jika doInBackground Anda kosong)
Peter
Anda dapat lebih menyederhanakan kode di atas jika kelas Anda memperluas AsyncTask <Void, Integer, Void>. Ini mulai bekerja dengan baik, terima kasih.
Peter Arandorenko
1
Terima kasih banyak. Saya mempunyai skenario di mana saya perlu menjalankan dua tugas async secara bersamaan untuk membaca data dari server web, tetapi saya menemukan server hanya menerima satu URL dan selama permintaan tertentu tidak selesai, url AsyncTask kedua tidak akan mengenai server . Jawaban Anda memecahkan masalah saya :)
Santhosh Gutta
1
Saya terus mendapatkan upvotes untuk ini, terima kasih. :) Tapi saya punya kesan banyak orang mencoba menggunakan AsyncTask untuk jaringan di mana perpustakaan yang sangat baik seperti Volley , Glide atau Swagger adalah pilihan yang lebih baik. Pastikan untuk memeriksa ini sebelum menggunakan AsyncTask :)
sulai
21

Membuat saran @sulai lebih umum:

@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
sumber
solusi yang lebih umum: AsyncTaskCompat.executeParallel (MyAsyncTask baru, myprams);
Vikash Kumar Verma
11

Hanya untuk memasukkan pembaruan terbaru (PEMBARUAN 4) dalam jawaban rapi @Arhimed dalam ringkasan @sulai yang sangat baik:

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
sumber
Oh wow, luar biasa. Ini berarti .executeOnExecutor()sekarang berlebihan untuk API> = KITKAT, ya?
Taslim Oseni
3

Itu mungkin. Versi perangkat android saya adalah 4.0.4 dan android.os.Build.VERSION.SDK_INT adalah 15

Saya punya 3 pemintal

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

Dan saya juga memiliki kelas Async-Tack.

Ini adalah kode pemuatan pemintal saya

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

Ini bekerja dengan sempurna. Satu per satu pemintal saya dimuat. Saya tidak pengguna mengeksekusi OnExecutor.

Ini kelas tugas Async saya

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
sumber
2
bagaimana Anda bahkan melihat bahwa mereka tidak dieksekusi secara berurutan?
Behelit
@behelit Apakah penting ketika sedang bekerja? Silakan baca jawaban yang diterima untuk lebih jelasnya stackoverflow.com/a/4072832/2345900
Sajitha Rathnayake
2
Maaf saya tidak mengerti. Jika Anda tidak dapat memastikan apakah async Anda berurutan atau benar-benar async maka itu penting. Juga, jawaban yang terhubung mengatakan untuk menggunakan metode executonexecutor, yang sebenarnya tidak Anda gunakan dalam jawaban Anda. Maaf jika saya melewatkan sesuatu.
Behelit
1
"Satu per satu pemintaku memuat." Anda benar-benar mengatakannya sendiri bahwa ini adalah eksekusi serial, bukan eksekusi paralel. Setiap AsyncTasks kami ditambahkan ke antrian dan diproses secara berurutan. Ini tidak menjawab pertanyaan OP.
Joshua Pinter
Ini jawaban yang sangat lama. Jika ini tidak berhasil, silakan merujuk jawaban yang diterima
Sajitha Rathnayake
3

jika Anda ingin menjalankan tugas paralel, Anda perlu memanggil metode executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "your task name")setelah Android versi 3.0; tetapi metode ini tidak ada sebelum Android 3.0 dan setelah 1.6 karena itu menjalankan paralel dengan sendirinya, Jadi saya sarankan Anda menyesuaikan kelas AsyncTask Anda sendiri dalam proyek Anda, untuk menghindari pengecualian melemparkan dalam versi Android yang berbeda.

alfa
sumber