Idealny sposób na anulowanie wykonywania AsyncTask

108

Uruchamiam zdalne operacje pobierania plików audio i odtwarzania plików audio w wątku w tle przy użyciu AsyncTask . Pasek Cancellablepostępu jest wyświetlany przez czas trwania operacji pobierania.

Chcę anulować / przerwać AsyncTaskprzebieg, gdy użytkownik anuluje (decyduje) operację. Jaki jest idealny sposób na załatwienie takiej sprawy?

Samuh
źródło

Odpowiedzi:

76

Właśnie odkrył, że AlertDialogs„s boolean cancel(...);używam wszędzie faktycznie nic nie robi. Wspaniały.
Więc...

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
źródło
55
zamiast tworzyć flagę logiczną do uruchamiania, nie mógłbyś jej usunąć i zrobić tego, podczas gdy (! isCanceled ()) ???
konfucjusz
36
Z dokumentacji dotyczącej onCancelled (): "Uruchamia się w wątku interfejsu użytkownika po wywołaniu Cancel (boolean) i zakończeniu doInBackground (Object [])." To „after” oznacza, że ​​ustawienie flagi w onCancelled i sprawdzanie w doInBackground nie ma sensu.
lopek
2
@confucius to prawda, ale w ten sposób wątek w tle nie zostanie przerwany, przypuśćmy, że podczas przesyłania obrazu proces przesyłania jest kontynuowany w tle i nie otrzymaliśmy wywołania onPostExecute.
umesz
1
@DanHulme Myślę, że odniosłem się do fragmentu kodu podanego w odpowiedzi, a nie do komentarza konfucjusza (co jest słuszne).
lopek
4
Tak, ta odpowiedź nie działa . W doInBackground wymienić while(running)z while(!isCancelled())jak mówili inni tutaj w komentarzach.
matt5784
76

Jeśli wykonujesz obliczenia :

  • Musisz isCancelled()okresowo sprawdzać .

Jeśli wykonujesz żądanie HTTP :

  • Zapisz instancję swojego HttpGetlub HttpPostgdzieś (np. Pole publiczne).
  • Po wezwaniu cancelzadzwoń request.abort(). Spowoduje to IOExceptionwrzucenie do twojego doInBackground.

W moim przypadku miałem klasę łącznika, której użyłem w różnych AsyncTasks. Dla uproszczenia dodałem nową abortAllRequestsmetodę do tej klasy i wywołałem ją bezpośrednio po wywołaniu cancel.

wrygiel
źródło
dziękuję, działa, ale jak uniknąć wyjątku w tym przypadku?
BegiPass
Musisz zadzwonić HttpGet.abort()z wątku w tle lub otrzymasz plik android.os.NetworkOnMainThreadException.
Heath Borders
@wrygiel Jeśli wykonujesz żądanie HTTP, nie powinieneś cancel(true)przerywać żądania? Z dokumentacji: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
Jeśli masz procesor zużywający operację w AsyncTask, musisz wywołać cancel(true). Użyłem go i działa.
SMMousavi
20

Chodzi o to, że wywołanie AsyncTask.cancel () wywołuje tylko funkcję onCancel w zadaniu. Tutaj chcesz obsłużyć żądanie anulowania.

Oto małe zadanie, którego używam do wywołania metody aktualizacji

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
źródło
2
To zadziała, ale logicznie rzecz biorąc, kiedy czekasz na odpowiedź serwera, a właśnie wykonałeś operację db, to powinno odzwierciedlać prawidłowe zmiany w Twojej aktywności. Napisałem o tym bloga, zobacz moją odpowiedź.
Vikas
4
Jak wspomniano w komentarzach do zaakceptowanej odpowiedzi, nie ma potrzeby tworzenia własnej runningflagi. AsyncTask ma wewnętrzną flagę, która jest ustawiana, gdy zadanie zostało anulowane. Wymień while (running)się while (!isCancelled()). developer.android.com/reference/android/os/… Więc w tym prostym przypadku nie musisz onCancelled()nadpisywać.
ToolmakerSteve
11

Proste: nie używaj pliku AsyncTask. AsyncTaskjest przeznaczony do krótkich operacji, które kończą się szybko (kilkadziesiąt sekund) i dlatego nie trzeba ich anulować. „Odtwarzanie plików audio” nie kwalifikuje się. Nie potrzebujesz nawet wątku w tle do zwykłego odtwarzania plików audio.

CommonsWare
źródło
czy sugerujesz użycie zwykłego wątku Java i „przerwanie” wykonywania wątku przy użyciu niestabilnej zmiennej boolowskiej - konwencjonalny sposób w Javie?
Samuh
34
Bez obrazy Mike, ale to nie jest akceptowalna odpowiedź. AsyncTask ma metodę anulowania i powinna działać. O ile wiem, tak nie jest - ale nawet jeśli robię to źle, to powinien istnieć właściwy sposób na anulowanie zadania. W przeciwnym razie metoda nie istniałaby. Nawet krótkie zadania mogą wymagać anulowania - mam działanie, w którym rozpoczyna się AsyncTask natychmiast po załadowaniu, a jeśli użytkownik odpowie natychmiast po otwarciu zadania, zobaczy wymuszone zamknięcie sekundę później, gdy zadanie się zakończy, ale bez kontekstu istnieje do użycia w swoim onPostExecute.
Eric Mill
10
@Klondike: Nie mam pojęcia, kim jest „Mike”. „ale to nie jest do przyjęcia odpowiedź” - zapraszamy do wyrażenia opinii. „AsyncTask ma metodę anulowania i powinna działać”. - anulowanie wątków w Javie było problemem od około 15 lat. Nie ma to nic wspólnego z Androidem. W odniesieniu do scenariusza „Wymuś zamknięcie”, można go rozwiązać za pomocą zmiennej boolowskiej, w której testujesz, onPostExecute()aby sprawdzić, czy powinieneś kontynuować pracę.
CommonsWare
1
@Tejaswi Yerukalapudi: Bardziej chodzi o to, że nic nie zrobi automatycznie. Zobacz zaakceptowaną odpowiedź na to pytanie.
CommonsWare
10
Należy okresowo sprawdzać metodę isCancelled w swoim doInBackground w AsyncTask. Znajduje się w dokumentacji: developer.android.com/reference/android/os/ ...
Christopher Perry,
4

Jedynym sposobem na to jest sprawdzenie wartości metody isCancelled () i zatrzymanie odtwarzania, gdy zwróci wartość true.

dbyrne
źródło
4

W ten sposób piszę moje AsyncTask,
kluczowym punktem jest dodanie 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
źródło
1
Zauważyłem, że po prostu wywołanie anulowania (true) w zadaniu asynchronicznym i sprawdzanie isCancelled () okresowo działa, ale w zależności od tego, co robi twoje zadanie, może zająć do 60 sekund, zanim zostanie przerwane. Dodanie Thread.sleep (1) umożliwia natychmiastowe przerwanie. (Async Task przechodzi raczej w stan Wait i nie jest natychmiast odrzucany). Dzięki za to.
John J Smith
0

Nasza globalna zmienna klasy AsyncTask

LongOperation LongOperationOdeme = new LongOperation();

I akcja KEYCODE_BACK, która przerywa AsyncTask

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

Mi to pasuje.

Göksel Güren
źródło
0

Nie lubię cancel(true)niepotrzebnie przerywać moich zadań asynchronicznych, ponieważ mogą one mieć zasoby do zwolnienia, takie jak zamykanie gniazd lub strumieni plików, zapisywanie danych do lokalnej bazy danych itp. Z drugiej strony miałem do czynienia z sytuacjami, w których zadanie async odmawia zakończenia się przez część czasu, na przykład czasami, gdy główne działanie jest zamykane i żądam zakończenia zadania asynchronicznego z wnętrza onPause()metody działania. Więc to nie jest kwestia zwykłego dzwonienia running = false. Muszę wybrać rozwiązanie mieszane: oba połączenia running = false, a następnie danie zadaniu asynchronicznego kilka milisekund na zakończenie, a następnie wywołanie albo cancel(false)lub 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;
}

Jako skutek uboczny, po doInBackground()zakończeniu, czasami onCancelled()metoda jest wywoływana, a czasami onPostExecute(). Ale przynajmniej gwarantowane jest zakończenie zadania asynchronicznego.

Piovezan
źródło
Wygląda na stan wyścigu.
msangel
0

W odniesieniu do odpowiedzi Yanchenko z 29 kwietnia „10: Użycie podejścia„ while (running) ”jest fajne, gdy kod w ramach„ doInBackground ”musi być wykonywany wiele razy podczas każdego wykonania AsyncTask. Jeśli twój kod pod „doInBackground” ma być wykonany tylko raz na wykonanie AsyncTask, zawinięcie całego kodu pod „doInBackground” w pętli „while (running)” nie zatrzyma działania kodu w tle (wątku w tle), gdy Sam AsyncTask jest anulowany, ponieważ warunek „while (running)” zostanie oceniony tylko wtedy, gdy cały kod wewnątrz pętli while zostanie wykonany przynajmniej raz. Powinieneś więc albo (a.) Rozbić swój kod pod 'doInBackground' na wiele bloków 'while (running)' lub (b.) Wykonać liczne 'isCancelled'https://developer.android.com/reference/android/os/AsyncTask.html .

Dla opcji (a.) Można więc zmodyfikować odpowiedź Janczenki w następujący sposób:

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

// ...

Dla opcji (b.) Twój kod w 'doInBackground' będzie wyglądał mniej więcej tak:

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
źródło