Cara ideal untuk membatalkan AsyncTask yang sedang dieksekusi

108

Saya menjalankan operasi pengambilan file audio dan pemutaran file audio jarak jauh di utas latar belakang menggunakan AsyncTask. SEBUAHCancellable progress bar ditampilkan untuk waktu pengambilan operasi berjalan.

Saya ingin membatalkan / membatalkan proses AsyncTaskketika pengguna membatalkan (memutuskan) operasi. Bagaimana cara yang ideal untuk menangani kasus seperti itu?

Samuh
sumber

Jawaban:

76

Hanya menemukan bahwa AlertDialogs's boolean cancel(...);saya telah menggunakan mana-mana sebenarnya tidak apa-apa. Bagus.
Begitu...

public class MyTask extends AsyncTask<Void, Void, Void> {

    private volatile boolean running = true;
    private final ProgressDialog progressDialog;

    public MyTask(Context ctx) {
        progressDialog = gimmeOne(ctx);

        progressDialog.setCancelable(true);
        progressDialog.setOnCancelListener(new OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                // actually could set running = false; right here, but I'll
                // stick to contract.
                cancel(true);
            }
        });

    }

    @Override
    protected void onPreExecute() {
        progressDialog.show();
    }

    @Override
    protected void onCancelled() {
        running = false;
    }

    @Override
    protected Void doInBackground(Void... params) {

        while (running) {
            // does the hard work
        }
        return null;
    }

    // ...

}
yanchenko
sumber
55
daripada membuat flag boolean untuk dijalankan, tidak bisakah Anda menghapusnya dan membuatnya sementara (! isCanceled ()) ???
confucius
36
Dari dokumen tentang onCancelled (): "Berjalan di thread UI setelah cancel (boolean) dipanggil dan doInBackground (Object []) telah selesai." Ini 'setelah' berarti bahwa menyetel flag di onCancelled dan memeriksa doInBackground tidak masuk akal.
lopek
2
@confucius itu benar, tetapi dengan cara ini utas latar belakang tidak terputus, misalkan saat mengunggah gambar, proses mengunggah berlanjut di latar belakang dan kami tidak mendapatkan panggilan onPostExecute.
umesh
1
@DanHulme Saya yakin saya merujuk pada cuplikan kode yang diberikan dalam jawaban, bukan pada komentar konfusius (yang benar).
lopek
4
Ya, jawaban ini tidak berhasil . Di doInBackground, ganti while(running)dengan yang while(!isCancelled())orang lain katakan di sini di komentar.
matt5784
76

Jika Anda melakukan perhitungan :

  • Anda harus memeriksanya isCancelled()secara berkala.

Jika Anda melakukan permintaan HTTP :

  • Simpan contoh Anda HttpGetatauHttpPost suatu tempat (mis. Bidang publik).
  • Setelah menelepon cancel, telepon request.abort(). Ini akan menyebabkan IOExceptionterlempar ke dalam Anda doInBackground.

Dalam kasus saya, saya memiliki kelas konektor yang saya gunakan di berbagai AsyncTasks. Untuk membuatnya tetap sederhana, saya menambahkan abortAllRequestsmetode baru ke kelas itu dan memanggil metode ini langsung setelah memanggil cancel.

wrygiel.dll
sumber
terima kasih, ini berhasil, tetapi bagaimana cara menghindari pengecualian dalam kasus ini?
begiPass
Anda harus menelepon HttpGet.abort()dari thread latar belakang atau Anda akan mendapatkan file android.os.NetworkOnMainThreadException.
Heath Borders
@wrygiel Jika Anda melakukan permintaan HTTP, bukankah harus cancel(true)menghentikan permintaan? Dari dokumentasi:If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.
Storo
HttpURLConnection.disconnect();
Oded Breiner
Jika Anda memiliki operasi yang memakan cpu di AsyncTask, Anda harus memanggil cancel(true). Saya menggunakannya dan berhasil.
SMMousavi
20

Masalahnya adalah panggilan AsyncTask.cancel () hanya memanggil fungsi onCancel dalam tugas Anda. Di sinilah Anda ingin menangani permintaan pembatalan.

Berikut adalah tugas kecil yang saya gunakan untuk memicu metode pembaruan

private class UpdateTask extends AsyncTask<Void, Void, Void> {

        private boolean running = true;

        @Override
        protected void onCancelled() {
            running = false;
        }

        @Override
        protected void onProgressUpdate(Void... values) {
            super.onProgressUpdate(values);
            onUpdate();
        }

        @Override
        protected Void doInBackground(Void... params) {
             while(running) {
                 publishProgress();
             }
             return null;
        }
     }
DonCroco
sumber
2
Ini akan berhasil, tetapi secara logis ketika Anda menunggu respons server, dan Anda baru saja melakukan operasi db, maka seharusnya mencerminkan perubahan yang benar pada aktivitas Anda. Saya menulis blog tentang itu, lihat jawaban saya.
Vikas
4
Seperti yang disebutkan dalam komentar pada jawaban yang diterima, Anda tidak perlu membuat runningpanji sendiri . AsyncTask memiliki tanda internal yang disetel saat tugas telah dibatalkan. Ganti while (running)dengan while (!isCancelled()). developer.android.com/reference/android/os/… Jadi dalam kasus sederhana ini, Anda tidak perlu onCancelled()menimpa.
ToolmakerSteve
11

Sederhana: jangan gunakan file AsyncTask. AsyncTaskdirancang untuk pengoperasian singkat yang berakhir dengan cepat (puluhan detik) dan karenanya tidak perlu dibatalkan. "Pemutaran file audio" tidak memenuhi syarat. Anda bahkan tidak memerlukan utas latar belakang untuk pemutaran file audio biasa.

CommonsWare
sumber
apakah Anda menyarankan agar kami menggunakan thread java biasa dan "batalkan" thread yang dijalankan menggunakan variabel boolean volatil - cara Java konvensional?
Samuh
34
Jangan tersinggung Mike, tapi itu bukan jawaban yang bisa diterima. AsyncTask memiliki metode pembatalan, dan itu akan bekerja. Sejauh yang saya tahu, memang tidak - tetapi meskipun saya melakukannya dengan salah, seharusnya ada cara yang tepat untuk membatalkan tugas. Metode tidak akan ada jika tidak. Dan bahkan tugas singkat mungkin perlu dibatalkan - Saya memiliki Aktivitas yang memulai AsyncTask segera setelah memuat, dan jika pengguna membalas segera setelah membuka tugas, mereka akan melihat Tutup Paksa sedetik kemudian saat tugas selesai tetapi tidak ada konteks ada untuk digunakan dalam onPostExecute-nya.
Eric Mill
10
@Klondike: Saya tidak tahu siapa "Mike" itu. "tapi itu bukan jawaban yang bisa diterima" - Anda dipersilakan untuk mengutarakan pendapat Anda. "AsyncTask memiliki metode pembatalan, dan itu akan bekerja." - membatalkan thread di Java telah menjadi masalah selama ~ 15 tahun. Ini tidak ada hubungannya dengan Android. Sehubungan dengan skenario "Tutup Paksa" Anda, itu dapat diselesaikan dengan variabel boolean, yang Anda uji onPostExecute()untuk melihat apakah Anda harus melanjutkan pekerjaan.
CommonsWare
1
@Tejaswi Yerukalapudi: Lebih dari itu ia tidak akan melakukan apa pun secara otomatis. Lihat jawaban yang diterima untuk pertanyaan ini.
CommonsWare
10
Anda harus memeriksa metode isCancelled secara berkala di doInBackground Anda di AsyncTask. Itu ada di sana di dokumen: developer.android.com/reference/android/os/…
Christopher Perry
4

Satu-satunya cara untuk melakukannya adalah dengan memeriksa nilai metode isCancelled () dan menghentikan pemutaran saat mengembalikan nilai true.

dbyrne
sumber
4

Ini adalah bagaimana saya menulis AsyncTask saya
, titik kuncinya adalah menambahkan Thread.sleep (1);

@Override   protected Integer doInBackground(String... params) {

        Log.d(TAG, PRE + "url:" + params[0]);
        Log.d(TAG, PRE + "file name:" + params[1]);
        downloadPath = params[1];

        int returnCode = SUCCESS;
        FileOutputStream fos = null;
        try {
            URL url = new URL(params[0]);
            File file = new File(params[1]);
            fos = new FileOutputStream(file);

            long startTime = System.currentTimeMillis();
            URLConnection ucon = url.openConnection();
            InputStream is = ucon.getInputStream();
            BufferedInputStream bis = new BufferedInputStream(is);

            byte[] data = new byte[10240]; 
            int nFinishSize = 0;
            while( bis.read(data, 0, 10240) != -1){
                fos.write(data, 0, 10240);
                nFinishSize += 10240;
                **Thread.sleep( 1 ); // this make cancel method work**
                this.publishProgress(nFinishSize);
            }              
            data = null;    
            Log.d(TAG, "download ready in"
                  + ((System.currentTimeMillis() - startTime) / 1000)
                  + " sec");

        } catch (IOException e) {
                Log.d(TAG, PRE + "Error: " + e);
                returnCode = FAIL;
        } catch (Exception e){
                 e.printStackTrace();           
        } finally{
            try {
                if(fos != null)
                    fos.close();
            } catch (IOException e) {
                Log.d(TAG, PRE + "Error: " + e);
                e.printStackTrace();
            }
        }

        return returnCode;
    }
Andrew Chen
sumber
1
Saya menemukan bahwa hanya memanggil batal (true) pada tugas asinkron dan memeriksa isCancelled () secara berkala memang berfungsi, tetapi tergantung pada apa yang dilakukan tugas Anda, dapat memakan waktu hingga 60 detik sebelum diinterupsi. Menambahkan Thread.sleep (1) memungkinkannya untuk segera diinterupsi. (Tugas Asinkron malah masuk ke status Tunggu dan tidak langsung dibuang). Terima kasih untuk ini.
John J Smith
0

Variabel kelas AsyncTask global kami

LongOperation LongOperationOdeme = new LongOperation();

Dan tindakan KEYCODE_BACK yang mengganggu AsyncTask

   @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            LongOperationOdeme.cancel(true);
        }
        return super.onKeyDown(keyCode, event);
    }

Ini bekerja untuk saya.

Göksel Güren
sumber
0

Saya tidak suka memaksa interupsi tugas asinkron saya dengan cancel(true)tidak perlu karena mereka mungkin memiliki sumber daya untuk dibebaskan, seperti menutup soket atau aliran file, menulis data ke database lokal dll. Di sisi lain, saya telah menghadapi situasi di mana tugas async menolak untuk menyelesaikan sendiri sebagian waktu, misalnya kadang-kadang ketika aktivitas utama ditutup dan saya meminta tugas asinkron untuk diselesaikan dari dalam metode aktivitas onPause(). Jadi, ini bukan hanya soal menelepon running = false. Saya harus mencari solusi campuran: keduanya memanggil running = false, lalu memberikan tugas asinkron beberapa milidetik untuk diselesaikan, dan kemudian memanggil salah satu cancel(false)atau cancel(true).

if (backgroundTask != null) {
    backgroundTask.requestTermination();
    try {
        Thread.sleep((int)(0.5 * 1000));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    if (backgroundTask.getStatus() != AsyncTask.Status.FINISHED) {
        backgroundTask.cancel(false);
    }
    backgroundTask = null;
}

Akibatnya, setelah doInBackground()selesai, terkadang onCancelled()metode ini dipanggil, dan terkadang onPostExecute(). Tapi setidaknya penghentian tugas asinkron dijamin.

Piovezan
sumber
Terlihat seperti kondisi balapan.
msangel
0

Dengan mengacu pada jawaban Yanchenko pada 29 April '10: Menggunakan pendekatan 'while (running)' adalah hal yang rapi ketika kode Anda di bawah 'doInBackground' harus dijalankan beberapa kali selama setiap eksekusi AsyncTask. Jika kode Anda di bawah 'doInBackground' harus dijalankan hanya sekali per eksekusi AsyncTask, menggabungkan semua kode Anda di bawah 'doInBackground' dalam loop 'while (running)' tidak akan menghentikan kode latar belakang (utas latar belakang) agar tidak berjalan ketika AsyncTask sendiri dibatalkan, karena kondisi 'while (running)' hanya akan dievaluasi setelah semua kode di dalam while loop telah dieksekusi setidaknya sekali. Karena itu, Anda harus (a.) Memecah kode Anda di bawah 'doInBackground' menjadi beberapa blok 'saat (menjalankan)' atau (b.) Melakukan banyak 'isCancelled'https://developer.android.com/reference/android/os/AsyncTask.html .

Untuk opsi (a.), Seseorang dapat mengubah jawaban Yanchenko sebagai berikut:

public class MyTask extends AsyncTask<Void, Void, Void> {

private volatile boolean running = true;

//...

@Override
protected void onCancelled() {
    running = false;
}

@Override
protected Void doInBackground(Void... params) {

    // does the hard work

    while (running) {
        // part 1 of the hard work
    }

    while (running) {
        // part 2 of the hard work
    }

    // ...

    while (running) {
        // part x of the hard work
    }
    return null;
}

// ...

Untuk opsi (b.) Kode Anda di 'doInBackground' akan terlihat seperti ini:

public class MyTask extends AsyncTask<Void, Void, Void> {

//...

@Override
protected Void doInBackground(Void... params) {

    // part 1 of the hard work
    // ...
    if (isCancelled()) {return null;}

    // part 2 of the hard work
    // ...
    if (isCancelled()) {return null;}

    // ...

    // part x of the hard work
    // ...
    if (isCancelled()) {return null;}
}

// ...
Alex Ivan Howard
sumber