Yürütülen bir AsyncTask'ı iptal etmenin ideal yolu


108

Kullanarak bir arka plan iş parçacığında uzaktan ses dosyası getirme ve ses dosyası oynatma işlemlerini çalıştırıyorum AsyncTask. CancellableGetirme işleminin çalıştığı süre için bir ilerleme çubuğu gösterilir.

AsyncTaskKullanıcı işlemi iptal ettiğinde (karşı karar verdiğinde) çalışmayı iptal etmek / iptal etmek istiyorum . Böyle bir vakayı halletmenin ideal yolu nedir?

Yanıtlar:


76

Sadece keşfetti AlertDialogs's boolean cancel(...);her yerde aslında kullanarak hiçbir şey yapmaz oldum. Harika.
Yani...

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

    // ...

}

55
çalıştırmak için bir boole bayrağı yapmak yerine, onu kaldırıp bunu while (! isCanceled ()) ???
confucius

36
OnCancelled () hakkındaki belgelerden: "Cancel ( boolean) çağrıldıktan ve doInBackground (Object []) bittikten sonra UI iş parçacığında çalışır ." Bu 'sonra', onCancelled'da bir bayrak ayarlamanın ve doInBackground'da kontrol etmenin bir anlam ifade etmediği anlamına gelir.
lopek

2
@confucius doğru, ancak bu şekilde arka plan iş parçacığı kesintiye uğramaz, varsayalım resim yüklerken durum, yükleme işlemi arka planda devam ediyor ve onPostExecute çağrılmadık.
umesh

1
@DanHulme Konfüçyüs'ün yorumuna değil (doğru olan) yanıtta verilen kod parçasına atıfta bulunduğuma inanıyorum.
lopek

4
Evet, bu cevap işe yaramıyor . DoInBackground'da, başkalarının burada yorumlarda söylediği gibi while(running)ile değiştirin while(!isCancelled()).
matt5784

76

Hesaplama yapıyorsanız :

  • isCancelled()Periyodik olarak kontrol etmelisiniz .

Bir HTTP isteği yapıyorsanız :

  • Örneğinizi HttpGetveya bir HttpPostyere kaydedin (ör. Bir kamu alanı).
  • Aradıktan sonra cancelarayın request.abort(). Bu IOExceptionsizin içine atılmasına neden olacaktır doInBackground.

Benim durumumda, çeşitli AsyncTasks'ta kullandığım bir bağlayıcı sınıfım vardı. Basit tutmak abortAllRequestsiçin o sınıfa yeni bir yöntem ekledim ve bu yöntemi çağırdıktan sonra doğrudan çağırdım cancel.


teşekkür ederim, işe yarıyor, ancak bu durumda istisnadan nasıl kaçınılır?
begiPass

HttpGet.abort()Bir arka plan iş parçacığından aramanız gerekir , yoksa bir android.os.NetworkOnMainThreadException.
Heath Sınırları

@wrygiel Bir HTTP isteği yapıyorsanız cancel(true), isteği yarıda kesmemeli mi? Dokümantasyondan: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

AsyncTask'ta cpu tüketen bir işleminiz varsa, bu yüzden aramanız gerekir cancel(true). Kullandım ve işe yarıyor.
SMMousavi

20

AsyncTask.cancel () çağrısının yalnızca görevinizdeki onCancel işlevini çağırmasıdır. İptal isteğini işlemek istediğiniz yer burasıdır.

İşte bir güncelleme yöntemini tetiklemek için kullandığım küçük bir görev

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

2
Bu işe yarayacaktır, ancak mantıksal olarak sunucu yanıtını beklediğinizde ve db işlemini gerçekleştirdiğinizde, etkinliğinize doğru değişiklikleri yansıtması gerekir. Bunun üzerine bir blog yazdım, cevabıma bakın.
Vikas

4
Kabul edilen cevaba ilişkin yorumlarda belirtildiği gibi, kendi runningbayrağınızı oluşturmanıza gerek yoktur . AsyncTask, görev iptal edildiğinde ayarlanan dahili bir bayrağa sahiptir. Değiştir while (running)ile while (!isCancelled()). developer.android.com/reference/android/os/… Yani bu basit durumda, onCancelled()geçersiz kılmanıza gerek yoktur .
ToolmakerSteve

11

Basit: bir AsyncTask. AsyncTaskhızlı biten (onlarca saniye) ve bu nedenle iptal edilmesi gerekmeyen kısa işlemler için tasarlanmıştır. "Ses dosyası oynatma" uygun değildir. Sıradan ses dosyası oynatmak için bir arka plan iş parçacığına bile ihtiyacınız yok.


normal java iş parçacığı kullanmamızı ve uçucu boolean değişkeni (geleneksel Java yöntemi) kullanarak iş parçacığı çalışmasını "iptal etmemizi" mi öneriyorsunuz?
Samuh

34
Alınma Mike, ama bu kabul edilebilir bir cevap değil. AsyncTask bir iptal yöntemine sahiptir ve çalışmalıdır. Anladığım kadarıyla öyle değil - ama yanlış yapsam bile, o zaman bir görevi iptal etmenin doğru bir yolu olmalı. Aksi takdirde yöntem var olmazdı. Ve kısa görevlerin bile iptal edilmesi gerekebilir - Yüklendikten hemen sonra AsyncTask'ın başladığı bir Activity'im var ve kullanıcı görevi açtıktan hemen sonra geri dönerse, görev bittiğinde bir saniye sonra bir Zorla Kapatmayı görecekler ancak bağlam yok onPostExecute içinde kullanmak için var.
Eric Mill

10
@Klondike: "Mike" ın kim olduğu hakkında hiçbir fikrim yok. "ama bu kabul edilebilir bir cevap değil" - fikrinize hoş geldiniz. "AsyncTask bir iptal yöntemine sahiptir ve çalışmalıdır." - Java'da iş parçacığı iptali ~ 15 yıldır bir sorun olmuştur. Android ile ilgisi yok. "Kapatmaya Zorla" senaryonuzla ilgili olarak, bu onPostExecute(), işe devam etmeniz gerekip gerekmediğini görmek için test ettiğiniz bir boole değişkeni ile çözülebilir .
CommonsWare

1
@Tejaswi Yerukalapudi: Otomatik olarak hiçbir şey yapmayacak olması daha önemli. Bu sorunun kabul edilen cevabına bakın.
CommonsWare

10
AsyncTask'taki doInBackground'unuzda isCancelled yöntemini periyodik olarak kontrol etmeniz gerekir. Dokümanlarda tam orada: developer.android.com/reference/android/os/…
Christopher Perry

4

Bunu yapmanın tek yolu, isCancelled () yönteminin değerini kontrol etmek ve doğruya döndüğünde oynatmayı durdurmaktır.


4

AsyncTask'ımı
bu şekilde yazıyorum , anahtar nokta add 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;
    }

1
Async görevde basitçe cancel (true) çağırmanın ve periyodik olarak isCancelled () 'i kontrol etmenin işe yaradığını, ancak görevinizin ne yaptığına bağlı olarak, kesintiye uğraması 60 saniye kadar sürebilir. Thread.sleep (1) eklenmesi, anında kesintiye uğramasını sağlar. (Zaman uyumsuz Görev bunun yerine Bekleme durumuna geçer ve hemen atılmaz). Bunun için teşekkürler.
John J Smith

0

Global AsyncTask sınıfı değişkenimiz

LongOperation LongOperationOdeme = new LongOperation();

Ve AsyncTask'ı kesen KEYCODE_BACK eylemi

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

Benim için çalışıyor.


0

Zaman uyumsuz görevlerimi cancel(true)gereksiz yere kesintiye uğratmaktan hoşlanmıyorum çünkü bunlar serbest bırakılacak kaynaklara sahip olabilir, örneğin soketleri veya dosya akışlarını kapatmak, yerel veritabanına veri yazmak vb. Öte yandan, zaman uyumsuz görev, örneğin bazen ana etkinlik kapatılırken ve zaman uyumsuz görevin etkinlik onPause()yönteminin içinden bitirmesini istediğimde, zamanın bir bölümünde kendisini bitirmeyi reddediyor . Yani bu sadece bir arama meselesi değil running = false. Karma bir çözüme gitmem gerekiyor: her ikisi de arayın running = false, sonra asenkron görevi bitirmek için birkaç milisaniye verin ve sonra ya cancel(false)da çağırın 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;
}

Bir yan sonuç olarak, doInBackground()bitirdikten sonra bazen onCancelled()yöntem çağrılır ve bazen onPostExecute(). Ancak en azından eşzamansız görev sonlandırma garantilidir.


Bir yarış durumuna benziyor.
msangel

0

Yanchenko'nun 29 Nisan '10'daki cevabına referansla: 'doInBackground' altındaki kodunuzun AsyncTask'ın her yürütülmesi sırasında birden çok kez çalıştırılması gerektiğinde 'while (koşarken)' yaklaşımını kullanmak düzgündür. 'DoInBackground' altındaki kodunuz, AsyncTask'ın her yürütülmesi için yalnızca bir kez yürütülmesi gerekiyorsa, tüm kodunuzu 'doInBackground' altında bir 'while (çalışırken)' döngüsüne sarmak arka plan kodunun (arka plan iş parçacığı) çalışmasını durdurmaz. AsyncTask'ın kendisi iptal edildi, çünkü 'while (çalışırken)' koşulu yalnızca while döngüsünün içindeki tüm kod en az bir kez çalıştırıldığında değerlendirilecektir. Bu nedenle ya (a.) 'DoInBackground' altındaki kodunuzu birden çok 'while (çalışırken)' bloğuna bölmeli veya (b.) Çok sayıda 'isCancelled' gerçekleştirmelisinizhttps://developer.android.com/reference/android/os/AsyncTask.html .

Seçenek (a.) İçin, Yanchenko'nun cevabı şu şekilde değiştirilebilir:

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

// ...

Seçenek (b.) İçin 'doInBackground'daki kodunuz şuna benzer:

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

// ...
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.