Идеальный способ отменить выполнение AsyncTask

108

Я выполняю операции удаленной выборки аудиофайлов и воспроизведения аудиофайлов в фоновом потоке, используя AsyncTask. Индикатор Cancellableвыполнения отображается на время выполнения операции выборки.

Я хочу отменить / прервать AsyncTaskвыполнение, когда пользователь отменяет (решает не выполнять) операцию. Как лучше всего поступить в таком случае?

Самух
источник

Ответы:

76

Просто обнаружил , что AlertDialogs«S boolean cancel(...);я использую везде на самом деле ничего не делает. Отлично.
Так...

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
вместо того, чтобы создавать логический флаг для запуска, не могли бы вы удалить его и сделать это while (! isCanceled ()) ???
confucius
36
Из документов о onCancelled (): «Выполняется в потоке пользовательского интерфейса после вызова cancel (boolean) и завершения doInBackground (Object [])». Это «после» означает, что установка флага в onCancelled и проверка в doInBackground не имеет смысла.
lopek
2
@confucius, это верно, но в этом случае фоновый поток не прерывается, предположим, что при загрузке изображения процесс загрузки продолжается в фоновом режиме, и мы не получили вызов onPostExecute.
umesh
1
@DanHulme Я считаю, что ссылался на фрагмент кода, указанный в ответе, а не на комментарий Конфуция (что верно).
lopek
4
Да, этот ответ не работает . В doInBackground замените while(running)на, while(!isCancelled())как говорили другие здесь в комментариях.
matt5784
76

Если вы выполняете вычисления :

  • Приходится isCancelled()периодически проверять .

Если вы выполняете HTTP-запрос :

  • Сохраните экземпляр вашего HttpGetили HttpPostгде-нибудь (например, публичное поле).
  • После звонка cancelзвоните request.abort(). Это вызовет IOExceptionпопадание в ваш файл doInBackground.

В моем случае у меня был класс коннектора, который я использовал в различных AsyncTasks. Для простоты я добавил abortAllRequestsк этому классу новый метод и вызвал этот метод сразу после вызова cancel.

wrygiel
источник
спасибо, работает, но как избежать исключения в этом случае?
BeginiPass 01
Вы должны позвонить HttpGet.abort()из фонового потока, иначе вы получите файл android.os.NetworkOnMainThreadException.
Heath Borders
@wrygiel Если вы выполняете HTTP-запрос, не следует ли его cancel(true)прерывать? Из документации: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();
Одед Брейнер
Если у вас есть операция, потребляющая процессор в AsyncTask, вы должны позвонить cancel(true). Я использовал его, и он работает.
SMMousavi
20

Дело в том, что вызов AsyncTask.cancel () вызывает только функцию onCancel в вашей задаче. Здесь вы хотите обработать запрос на отмену.

Вот небольшая задача, которую я использую для запуска метода обновления

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
источник
2
Это будет работать, но логически, когда вы ждете ответа сервера и только что выполнили операцию с базой данных, это должно отражать правильные изменения в вашей активности. Я написал об этом блог, посмотрите мой ответ.
Vikas
4
Как упоминалось в комментариях к принятому ответу, нет необходимости создавать собственный runningфлаг. AsyncTask имеет внутренний флаг, который устанавливается, когда задача была отменена. Заменить while (running)на while (!isCancelled()). developer.android.com/reference/android/os/… Итак, в этом простом случае вам не нужно onCancelled()переопределять.
ToolmakerSteve
11

Просто: не используйте расширение AsyncTask. AsyncTaskпредназначен для коротких операций, которые быстро завершаются (десятки секунд) и поэтому не требуют отмены. «Воспроизведение аудиофайлов» не подходит. Вам даже не нужен фоновый поток для обычного воспроизведения аудиофайлов.

CommonsWare
источник
Вы предлагаете, чтобы мы использовали обычный поток Java и «прервали» выполнение потока с использованием изменчивой логической переменной - обычным способом Java?
Самух
34
Без обид, Майк, но это неприемлемый ответ. AsyncTask имеет метод отмены, и он должен работать. Насколько я могу судить, это не так, но даже если я делаю это неправильно, должен быть правильный способ отменить задачу. В противном случае метода не существовало бы. И даже короткие задачи могут нуждаться в отмене - у меня есть Activity, в котором она начинает AsyncTask сразу после загрузки, и если пользователь ответит сразу после открытия задачи, они увидят Force Close через секунду, когда задача завершится, но без контекста существует для использования в своем onPostExecute.
Эрик Милл
10
@Klondike: Я понятия не имею, кто такой "Майк". «но это неприемлемый ответ» - вы можете выслушать свое мнение. «AsyncTask имеет метод отмены, и он должен работать». - отмена потоков в Java была проблемой около 15 лет. Это не имеет ничего общего с Android. Что касается вашего сценария "принудительного закрытия", это может быть решено с помощью логической переменной, которую вы проверяете, onPostExecute()чтобы увидеть, следует ли вам продолжать работу.
CommonsWare
1
@Tejaswi Yerukalapudi: Скорее, он ничего не сделает автоматически. См. Принятый ответ на этот вопрос.
CommonsWare
10
Вы должны периодически проверять метод isCancelled в своем doInBackground на AsyncTask. Это прямо в документации: developer.android.com/reference/android/os/…
Кристофер Перри
4

Единственный способ сделать это - проверить значение метода isCancelled () и остановить воспроизведение, когда он вернет true.

dbyrne
источник
4

Вот как я пишу свою AsyncTask,
ключевой момент - добавить 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
Я обнаружил, что простой вызов cancel (true) для задачи async и периодическая проверка isCancelled () действительно работает, но в зависимости от того, что делает ваша задача, может потребоваться до 60 секунд, прежде чем она будет прервана. Добавление Thread.sleep (1) позволяет немедленно прервать его. (Асинхронная задача скорее переходит в состояние ожидания и не сбрасывается немедленно). Спасибо за это.
Джон Дж. Смит,
0

Наша глобальная переменная класса AsyncTask

LongOperation LongOperationOdeme = new LongOperation();

И действие KEYCODE_BACK, которое прерывает AsyncTask

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

Меня устраивает.

Гёксель Гюрен
источник
0

Я не люблю принудительно прерывать мои асинхронные задачи без cancel(true)необходимости, потому что у них могут быть ресурсы, которые нужно освободить, например, закрытие сокетов или файловых потоков, запись данных в локальную базу данных и т. Д. С другой стороны, я сталкивался с ситуациями, в которых Задача async отказывается завершать себя часть времени, например, иногда, когда основное действие закрывается, и я запрашиваю завершение задачи async изнутри onPause()метода действия. Так что дело не в простом звонке running = false. Мне нужно найти смешанное решение: оба вызывают running = false, затем дают асинхронной задаче несколько миллисекунд для завершения, а затем вызывают либо cancel(false)или 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;
}

Как побочный результат, после doInBackground()финиша иногда onCancelled()вызывается метод, а иногда onPostExecute(). Но как минимум гарантировано завершение асинхронной задачи.

Пиовезан
источник
Похоже на состояние гонки.
msangel 03
0

Ссылаясь на ответ Янченко от 29 апреля 2010 г .: Использование подхода «пока (выполняется)» удобно, когда ваш код под «doInBackground» должен выполняться несколько раз во время каждого выполнения AsyncTask. Если ваш код в doInBackground должен выполняться только один раз за выполнение AsyncTask, перенос всего вашего кода в doInBackground в цикл while (running) не остановит выполнение фонового кода (фонового потока), когда Сам AsyncTask отменяется, потому что условие while (running) будет оценено только после того, как весь код внутри цикла while будет выполнен хотя бы один раз. Таким образом, вы должны либо (а.) Разбить свой код в разделе «doInBackground» на несколько блоков «пока (выполняется)» или (б.) Выполнить множество «isCancelled».https://developer.android.com/reference/android/os/AsyncTask.html .

Таким образом, для варианта (а) ответ Янченко можно изменить следующим образом:

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

// ...

Для варианта (b.) Ваш код в doInBackground будет выглядеть примерно так:

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

// ...
Алекс Иван Ховард
источник