Batas utas Android AsyncTask?

95

Saya mengembangkan aplikasi di mana saya perlu memperbarui beberapa info setiap kali pengguna masuk ke sistem, saya juga menggunakan database di telepon. Untuk semua operasi itu (pembaruan, mengambil data dari db, dll.) Saya menggunakan tugas async. Sampai sekarang saya tidak melihat mengapa saya tidak boleh menggunakannya, tetapi baru-baru ini saya mengalami bahwa jika saya melakukan beberapa operasi, beberapa tugas asinkron saya hanya berhenti pada pra-eksekusi dan tidak melompat ke doInBackground. Itu terlalu aneh untuk dibiarkan seperti itu, jadi saya mengembangkan aplikasi sederhana lain hanya untuk memeriksa apa yang salah. Dan cukup aneh, saya mendapatkan perilaku yang sama ketika jumlah total tugas async mencapai 5, yang keenam berhenti pada pra-eksekusi.

Apakah android memiliki batasan asyncTasks pada Aktivitas / Aplikasi? Atau hanya beberapa bug dan harus dilaporkan? Apakah ada yang mengalami masalah yang sama dan mungkin menemukan solusi untuk itu?

Ini kodenya:

Cukup buat 5 utas tersebut untuk bekerja di latar belakang:

private class LongAsync extends AsyncTask<String, Void, String>
{
    @Override
    protected void onPreExecute()
    {
        Log.d("TestBug","onPreExecute");
        isRunning = true;
    }

    @Override
    protected String doInBackground(String... params)
    {
        Log.d("TestBug","doInBackground");
        while (isRunning)
        {

        }
        return null;
    }

    @Override
    protected void onPostExecute(String result)
    {
        Log.d("TestBug","onPostExecute");
    }
}

Dan kemudian buat utas ini. Ini akan masuk ke preExecute dan hang (tidak akan masuk ke doInBackground).

private class TestBug extends AsyncTask<String, Void, String>
{
    @Override
    protected void onPreExecute()
    {
        Log.d("TestBug","onPreExecute");

        waiting = new ProgressDialog(TestActivity.this);
        waiting.setMessage("Loading data");
        waiting.setIndeterminate(true);
        waiting.setCancelable(true);
        waiting.show();
    }

    @Override
    protected String doInBackground(String... params)
    {
        Log.d("TestBug","doInBackground");
        return null;
    }

    @Override
    protected void onPostExecute(String result)
    {
        waiting.cancel();
        Log.d("TestBug","onPostExecute");
    }
}
Mindaugas Svirskas
sumber

Jawaban:

207

Semua AsyncTasks dikontrol secara internal oleh ThreadPoolExecutor (statis) bersama dan LinkedBlockingQueue . Saat Anda memanggil executeAsyncTask, perintah ThreadPoolExecutorakan mengeksekusinya saat siap di masa mendatang.

'Kapan saya siap?' perilaku a ThreadPoolExecutordikendalikan oleh dua parameter, ukuran kolam inti dan ukuran kolam maksimum . Jika ada kurang dari utas ukuran kumpulan inti yang saat ini aktif dan pekerjaan baru masuk, pelaksana akan membuat utas baru dan segera menjalankannya. Jika ada setidaknya utas ukuran kumpulan inti yang berjalan, itu akan mencoba untuk mengantri pekerjaan dan menunggu sampai ada utas menganggur tersedia (yaitu sampai pekerjaan lain selesai). Jika tidak memungkinkan untuk memasukkan pekerjaan ke dalam antrian (antrian dapat memiliki kapasitas maksimal), ini akan membuat thread baru (hingga thread ukuran kumpulan maksimum) untuk pekerjaan yang dijalankan. Thread non-inti yang menganggur pada akhirnya dapat dinonaktifkan menurut parameter batas waktu tetap hidup.

Sebelum Android 1.6, ukuran kumpulan inti adalah 1 dan ukuran kumpulan maksimum adalah 10. Sejak Android 1.6, ukuran kumpulan inti adalah 5, dan ukuran kumpulan maksimum adalah 128. Ukuran antrean adalah 10 dalam kedua kasus. Batas waktu keep-hidup adalah 10 detik sebelum 2,3, dan 1 detik sejak itu.

Dengan semua pemikiran ini, sekarang menjadi jelas mengapa AsyncTaskkehendak hanya muncul untuk menjalankan 5/6 tugas Anda. Tugas ke-6 sedang diantrekan hingga salah satu tugas lainnya selesai. Ini adalah alasan yang sangat bagus mengapa Anda tidak boleh menggunakan AsyncTasks untuk operasi yang berjalan lama - ini akan mencegah AsyncTasks lain berjalan.

Untuk kelengkapan, jika Anda mengulangi latihan Anda dengan lebih dari 6 tugas (misalnya 30), Anda akan melihat bahwa lebih dari 6 akan masuk doInBackgroundkarena antrian akan menjadi penuh dan pelaksana didorong untuk membuat lebih banyak utas pekerja. Jika Anda terus menjalankan tugas yang berjalan lama, Anda akan melihat bahwa 20/30 menjadi aktif, dengan 10 masih dalam antrian.

antonyt
sumber
2
"Ini adalah alasan yang sangat bagus mengapa Anda tidak boleh menggunakan AsyncTasks untuk operasi yang berjalan lama" Apa rekomendasi Anda untuk skenario ini? Memunculkan utas baru secara manual atau membuat layanan eksekutor Anda sendiri?
user123321
2
Pelaksana pada dasarnya adalah abstraksi di atas utas, mengurangi kebutuhan untuk menulis kode kompleks untuk mengelolanya. Ini memisahkan tugas Anda dari bagaimana mereka harus dijalankan. Jika kode Anda hanya bergantung pada pelaksana, maka mudah untuk secara transparan mengubah berapa banyak utas yang digunakan, dll. Saya tidak dapat benar-benar memikirkan alasan yang baik untuk membuat utas sendiri, karena bahkan untuk tugas sederhana, jumlah pekerjaan dengan seorang Pelaksana adalah sama, jika tidak kurang.
antonyt
37
Harap diperhatikan bahwa dari Android 3.0+, jumlah default AsyncTasks bersamaan telah dikurangi menjadi 1. Info selengkapnya: developer.android.com/reference/android/os/…
Kieran
Wow, terima kasih banyak atas jawaban yang bagus. Akhirnya saya mendapat penjelasan mengapa kode saya gagal secara sporadis dan misterius.
Chris Knight
@antonyt, Satu keraguan lagi, AsyncTasks Dibatalkan, apakah itu akan dihitung ke jumlah AsyncTasks? yaitu, dihitung dalam core pool sizeatau maximum pool size?
nmxprime
9

@antonyt memiliki jawaban yang benar tetapi jika Anda mencari solusi sederhana maka Anda dapat memeriksa Needle.

Dengan itu Anda dapat menentukan ukuran kumpulan utas khusus dan, tidak seperti AsyncTask, ini berfungsi pada semua versi Android yang sama. Dengannya Anda dapat mengatakan hal-hal seperti:

Needle.onBackgroundThread().withThreadPoolSize(3).execute(new UiRelatedTask<Integer>() {
   @Override
   protected Integer doWork() {
       int result = 1+2;
       return result;
   }

   @Override
   protected void thenDoUiRelatedWork(Integer result) {
       mSomeTextView.setText("result: " + result);
   }
});

atau hal-hal seperti itu

Needle.onMainThread().execute(new Runnable() {
   @Override
   public void run() {
       // e.g. change one of the views
   }
}); 

Ia dapat melakukan lebih banyak lagi. Lihat di GitHub .

Zsolt Safrany
sumber
komit terakhir adalah 5 tahun yang lalu :(
LukaszTaraszka
5

Pembaruan : Sejak API 19, ukuran kumpulan utas inti diubah untuk mencerminkan jumlah CPU pada perangkat, dengan minimum 2 dan maksimum 4 pada awalnya, sambil berkembang ke maksimum CPU * 2 +1 - Referensi

// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;

Perhatikan juga bahwa meskipun eksekutor default AsyncTask adalah serial (menjalankan satu tugas pada satu waktu dan dalam urutan kedatangannya), dengan metode

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
        Params... params)

Anda dapat menyediakan Pelaksana untuk menjalankan tugas Anda. Anda dapat memberikan THREAD_POOL_EXECUTOR sebagai pelaksana di bawah tenda tetapi tanpa serialisasi tugas, atau Anda bahkan dapat membuat Pelaksana Anda sendiri dan menyediakannya di sini. Namun, perhatikan dengan cermat peringatan di Javadocs.

Peringatan: Mengizinkan banyak tugas untuk berjalan secara paralel dari kumpulan utas biasanya bukan yang diinginkan, karena urutan operasinya tidak ditentukan. Misalnya, jika tugas ini digunakan untuk mengubah keadaan yang sama (seperti menulis file karena klik tombol), tidak ada jaminan untuk urutan modifikasi. Tanpa kerja yang cermat, dalam kasus yang jarang terjadi, versi data yang lebih baru dapat ditimpa oleh versi yang lebih lama, yang menyebabkan hilangnya data dan masalah stabilitas. Perubahan tersebut paling baik dilakukan secara serial; untuk menjamin pekerjaan tersebut diserialkan terlepas dari versi platformnya, Anda dapat menggunakan fungsi ini dengan SERIAL_EXECUTOR.

Satu hal lagi yang perlu diperhatikan adalah bahwa kerangka kerja yang disediakan Pelaksana THREAD_POOL_EXECUTOR dan versi serinya SERIAL_EXECUTOR (yang merupakan default untuk AsyncTask) bersifat statis (konstruksi tingkat kelas) dan karenanya dibagikan di semua instance AsyncTask (s) di seluruh proses aplikasi Anda.

rnk
sumber