Выполнить AsyncTask несколько раз

127

В своей деятельности я использую класс, расширяющий AsyncTask, и параметр, являющийся экземпляром этой AsyncTask. Когда звоню, mInstanceOfAT.execute("")все нормально. Но приложение вылетает, когда я нажимаю кнопку обновления, которая снова вызывает AsyncTask (в случае, если сетевое задание не работает). Причина тогда появляется исключение, которое говорит

Невозможно выполнить задачу: задача уже выполнена (задача может быть выполнена только один раз)

Я попытался вызвать cancel (true) для экземпляра Asyctask, но он тоже не работает. Единственное решение на данный момент - создание новых экземпляров Asyntask. Это правильный путь?

Спасибо.

Dayerman
источник

Ответы:

217

AsyncTask экземпляры можно использовать только один раз.

Вместо этого просто назовите свою задачу как new MyAsyncTask().execute("");

Из документов API AsyncTask:

Правила потоковой передачи

Для правильной работы этого класса необходимо соблюдать несколько правил потоковой передачи:

  • Экземпляр задачи должен быть создан в потоке пользовательского интерфейса.
  • execute (Params ...) должен быть вызван в потоке пользовательского интерфейса.
  • Не вызывайте onPreExecute (), onPostExecute (Result), doInBackground (Params ...), onProgressUpdate (Progress ...) вручную.
  • Задача может быть выполнена только один раз (при второй попытке выполнения будет сгенерировано исключение).
Стив Прентис
источник
2
То, что я сказал, что сделал, это единственная возможность? Потому что я хочу сохранить память, а не создавать новый объект.
Dayerman
1
См. Также stackoverflow.com/questions/2711183/…
Стив Прентис,
@StevePrentice: если я создаю экземпляр задачи каждые x секунд с помощью new task (). Execute (param), для отправки данных на сервер, как сборщик мусора может освободить память после завершения выполнения?
Ant4res
3
@ Ant4res, пока вы не ссылаетесь на экземпляр асинхронной задачи, GC освободит память. Однако, если у вас есть текущая фоновая задача, вы можете рассмотреть возможность выполнения ее в цикле внутри doInBackground и сделать вызовы publishProgress для обновления хода выполнения. Или другой подход - поместить вашу задачу в фоновый поток. Здесь много разных подходов, но я не могу рекомендовать один без подробностей.
Стив Прентис
28

Причины запускать и забывать экземпляры ASyncTask довольно подробно описаны в ответе Стива Прентиса. Однако, хотя вы ограничены тем, сколько раз вы выполняете ASyncTask, вы можете делать то, что вам нравится, пока поток работает. ,

Поместите исполняемый код в цикл внутри doInBackground () и используйте параллельную блокировку для запуска каждого выполнения. Вы можете получить результаты, используя publishProgress () / onProgressUpdate () .

Пример:

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

Конечно, это немного сложнее, чем традиционное использование ASyncTask, и вы отказываетесь от использования publishProgress () для фактического отчета о ходе выполнения. Но если вас беспокоит память, то этот подход гарантирует, что во время выполнения в куче останется только одна ASyncTask.

seanhodges
источник
Но дело в том, что я не хочу повторно запускать Asyntask во время работы, а потому, что у этого есть завершение и не было получено данных, как должно, а затем вызвать его снова.
Dayerman
Фактически вы выполняете ASyncTask только один раз таким образом, и вы можете проверить правильность данных внутри метода onPublishProgress (или делегировать проверку где-нибудь еще). Некоторое время назад я использовал этот шаблон для решения аналогичной проблемы (множество задач запускалось в быстрой последовательности, рискуя размером кучи).
seanhodges
Но что делать, если в этот момент сервер не отвечает, и я хочу повторить попытку через 10 секунд? AsyncTask уже завершена, верно? Тогда мне нужно позвонить еще раз
Дайерман
Я добавил пример кода, чтобы описать то, что я имею в виду. ASyncTask завершится только после того, как вы довольны результатом и вызовете terminateTask ().
seanhodges
1
Если вы получаете IllegalMonitorStateExceptionв runAgain(вызывается onProgressUpdate) увидеть этот ответ: stackoverflow.com/a/42646476/2711811 . Это предполагает (и сработало для меня), что signal()нужно окружить lock/ unlock. Возможно, это связано со временем publishProgressзвонка onProgressUpdate.
Энди
2

Я была такая же проблема. В моем случае у меня есть задача, которую я хочу выполнять onCreate()вonResume( ). Итак, я сделал свой Asynctask статическим и получил от него экземпляр. Теперь у нас все еще та же проблема.

Итак, что я сделал в onPostExecute (), так это:

instance = null;

Помня, что я проверяю статический метод getInstance, что мой экземпляр не равен нулю, иначе я создаю его:

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

Метод в postExecute очистит экземпляр и создаст его заново. Конечно, это можно делать вне класса.

SAMUELD
источник
1

Я сделал свои задачи вращения статическими, что затем помогло мне присоединять, отсоединять и повторно прикреплять их к потокам пользовательского интерфейса при изменении вращения. Но возвращаясь к вашему вопросу, я создаю флаг, чтобы увидеть, запущен ли поток. Когда вы хотите перезапустить поток, я проверяю, запущена ли задача вращения, если это предупреждение. Если это не так, я делаю его нулевым, а затем создаю новый, который будет работать с ошибкой, которую вы видите. Кроме того, после успешного завершения я обнуляю завершенную задачу с учетом ротации, чтобы она снова была готова к работе.

Арнаб К.
источник
0

Да, это правда, в документе говорится, что может быть выполнена только одна Asyntask.

Каждый раз, когда вам нужно его использовать, вы должны указать:

// 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> {
}
Виктор Руис.
источник