Wykonaj AsyncTask kilka razy

128

W moim działaniu używam klasy, która pochodzi od AsyncTask i parametru, który jest wystąpieniem tego AsyncTask. Kiedy dzwonię, mInstanceOfAT.execute("")wszystko jest w porządku. Ale aplikacja ulega awarii, gdy naciskam przycisk aktualizacji, który ponownie wywołuje AsyncTask (na wypadek, gdyby zadanie sieciowe nie działało). Bo wtedy pojawia się Wyjątek, który mówi

Nie można wykonać zadania: zadanie zostało już wykonane (zadanie można wykonać tylko raz)

Próbowałem wywołać anulowanie (true) dla wystąpienia Asyctask, ale to też nie działa. Jak dotąd jedynym rozwiązaniem jest utworzenie nowych instancji Asyntask. Czy to właściwy sposób?

Dzięki.

Dayerman
źródło

Odpowiedzi:

218

AsyncTask instancje mogą być użyte tylko raz.

Zamiast tego po prostu nazwij swoje zadanie w stylu new MyAsyncTask().execute("");

Z dokumentacji AsyncTask API:

Zasady dotyczące gwintowania

Istnieje kilka reguł wątków, których należy przestrzegać, aby ta klasa działała poprawnie:

  • Instancja zadania musi zostać utworzona w wątku interfejsu użytkownika.
  • execute (Params ...) musi być wywoływana w wątku interfejsu użytkownika.
  • Nie wywołuj onPreExecute (), onPostExecute (Result), doInBackground (Params ...), onProgressUpdate (Progress ...) ręcznie.
  • Zadanie można wykonać tylko raz (w przypadku próby drugiego wykonania zostanie zgłoszony wyjątek).
Steve Prentice
źródło
2
To, co powiedziałem, zrobiłem, czy to jedyna możliwość? Bo chcę zachować pamięć, zamiast tworzyć nowy obiekt.
Dayerman
1
Zobacz także stackoverflow.com/questions/2711183/…
Steve Prentice
@StevePrentice: jeśli tworzę instancję zadania co x sekund z new task (). Execute (param), aby wysłać dane do serwera, w jaki sposób garbage collector może zwolnić pamięć po zakończeniu wykonywania?
Ant4res
3
@ Ant4res, Dopóki nie odwołujesz się do wystąpienia zadania asynchronicznego, GC zwolni pamięć. Jeśli jednak masz trwające zadanie w tle, możesz rozważyć zrobienie tego w pętli wewnątrz doInBackground i wywołać metodę publicProgress w celu zaktualizowania postępu. Lub innym podejściem byłoby umieszczenie zadania w wątku w tle. Wiele różnych podejść tutaj, ale nie mogę polecić jednego bez szczegółów.
Steve Prentice
28

Powody wystrzeliwania i zapomnienia instancji ASyncTask są dość dobrze opisane w odpowiedzi Steve'a Prentice'a - jednak chociaż jesteś ograniczony co do tego, ile razy wykonujesz ASyncTask, możesz robić, co chcesz, podczas gdy wątek jest uruchomiony. .

Umieść swój kod wykonywalny w pętli wewnątrz metody doInBackground () i użyj blokady współbieżnej, aby wyzwolić każde wykonanie. Wyniki można pobrać za pomocą funkcji publicProgress () / onProgressUpdate () .

Przykład:

class GetDataFromServerTask extends AsyncTask<Input, Result, Void> {

    private final ReentrantLock lock = new ReentrantLock();
    private final Condition tryAgain = lock.newCondition();
    private volatile boolean finished = false;

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

        lock.lockInterruptibly();

        do { 
            // This is the bulk of our task, request the data, and put in "result"
            Result result = ....

            // Return it to the activity thread using publishProgress()
            publishProgress(result);

            // At the end, we acquire a lock that will delay
            // the next execution until runAgain() is called..
            tryAgain.await();

        } while(!finished);

        lock.unlock();
    }

    @Override
    protected void onProgressUpdate(Result... result) 
    {
        // Treat this like onPostExecute(), do something with result

        // This is an example...
        if (result != whatWeWant && userWantsToTryAgain()) {
            runAgain();
        }
    }

    public void runAgain() {
        // Call this to request data from the server again
        tryAgain.signal();
    }

    public void terminateTask() {
        // The task will only finish when we call this method
        finished = true;
        lock.unlock();
    }

    @Override
    protected void onCancelled() {
        // Make sure we clean up if the task is killed
        terminateTask();
    }
}

Oczywiście jest to nieco bardziej skomplikowane niż tradycyjne użycie ASyncTask i rezygnuje się z użycia funkcji PublishProgress () do raportowania rzeczywistych postępów. Ale jeśli martwisz się pamięcią, to podejście zapewni, że tylko jedno ASyncTask pozostanie w stercie w czasie wykonywania.

seanhodges
źródło
Ale chodzi o to, że nie chcę ponownie wykonywać zadania Asyntask, gdy jest uruchomione, ale w przypadku, gdy ten zakończył się i nie otrzymał danych tak, jak powinien, wywołaj go ponownie.
Dayerman
W rzeczywistości wykonujesz ASyncTask tylko raz w ten sposób i możesz sprawdzić, czy dane są poprawne w metodzie onPublishProgress (lub delegować kontrolę gdzie indziej). Użyłem tego wzorca do podobnego problemu jakiś czas temu (wiele zadań było uruchamianych w krótkich odstępach czasu, ryzykując rozmiar sterty).
seanhodges
Ale co, jeśli w tym momencie serwer nie odpowiada i chcę spróbować ponownie 10 sekund później? AsyncTask już się skończył, prawda? Wtedy muszę zadzwonić ponownie
Dayerman
Dodałem przykładowy kod, aby opisać, co mam na myśli. ASyncTask zakończy się dopiero wtedy, gdy będziesz zadowolony z wyniku i wywoła „terminateTask ()”.
seanhodges
1
Jeśli pojawi się IllegalMonitorStateExceptionw runAgain(nazywany przez onProgressUpdate) zobaczyć tę odpowiedź: stackoverflow.com/a/42646476/2711811 . Sugeruje (i zadziałało dla mnie), że signal()trzeba otaczać lock/ unlock. Może to mieć związek z czasem publishProgressdzwonienia onProgressUpdate.
Andy
2

Miałem ten sam problem. W moim przypadku mam zadanie, które chcę wykonać w onCreate()i w onResume(środku). Więc zrobiłem mój Asynctask statyczny i pobrałem z niego instancję. Teraz nadal mamy ten sam problem.

Więc to, co zrobiłem w onPostExecute (), to:

instance = null;

Pamiętając, że sprawdzam w statycznej metodzie getInstance, że moja instancja nie jest pusta, w przeciwnym razie ją tworzę:

if (instance == null){
    instance = new Task();
}
return instance;

Metoda w postExecute opróżni instancję i utworzy ją ponownie. Oczywiście można to zrobić poza zajęciami.

Samuel D.
źródło
1

Moje zadania rotacji stały się statyczne, co pomogło mi następnie dołączyć, odłączyć i ponownie dołączyć je do wątków interfejsu użytkownika przy zmianach rotacji. Ale wracając do twojego pytania, tworzę flagę, aby sprawdzić, czy wątek jest uruchomiony. Kiedy chcesz zrestartować wątek, sprawdzam, czy zadanie rotacji jest uruchomione, jeśli tak, to ostrzeżenie. Jeśli tak nie jest, ustawiam go na zero, a następnie tworzę nowy, który obejdzie błąd, który widzisz. Ponadto, po pomyślnym zakończeniu, anuluję ukończone zadanie świadome rotacji, aby było gotowe do ponownego wykonania.

Arnab C.
źródło
0

Tak, to prawda, doktor mówi, że można wykonać tylko jedno Asyntask.

Za każdym razem, gdy chcesz go użyć, musisz wykonać:

// Any time if you need to call her
final FirmwareDownload fDownload = new FirmwareDownload();
fDownload.execute("your parameter");

static class FirmwareDownload extends AsyncTask<String, String, String> {
}
Victor Ruiz.
źródło