Ідеальний спосіб скасувати виконання 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
замість того, щоб створити логічний прапор для запуску, ви не змогли його видалити та зробити це (! isCanceled ()) ???
конфуцій

36
З документів про onCancegled (): "Запускається потік інтерфейсу після скасування (булева), і doInBackground (Object []) закінчується." Це "після" означає, що встановлювати прапор у onCancepted та перевіряти в doInBackground немає сенсу.
lopek

2
@confucius правильно, але таким чином фонова нитка не переривається, припустимо, випадок, коли ви завантажуєте зображення, процес завантаження триває у фоновому режимі, і ми не отримали виклик на PoostExecute.
umesh

1
@DanHulme Я вважаю, що я посилався на фрагмент коду, наданий у відповіді, а не на коментар Конфуція (що правильно).
lopek

4
Так, ця відповідь не працює . У doInBackground замініть while(running)на те, while(!isCancelled())як тут сказали інші у коментарях.
matt5784

76

Якщо ви робите обчислення :

  • Ви повинні isCancelled()періодично перевіряти .

Якщо ви робите запит HTTP :

  • Збережіть екземпляр свого HttpGetабо HttpPostдесь (наприклад, загальнодоступного поля).
  • Після дзвінка cancelдзвоніть request.abort(). Це призведе до того, що IOExceptionвас кинуть всередині вашого doInBackground.

У моєму випадку у мене був клас з'єднувачів, який я використовував у різних AsyncTasks. Щоб зробити це просто, я додав новий abortAllRequestsметод до цього класу і назвав цей метод безпосередньо після виклику cancel.


дякую, це працює, але як уникнути винятку в цьому випадку?
begiPass

Вам потрібно зателефонувати HttpGet.abort()з фонової нитки, або ви отримаєте android.os.NetworkOnMainThreadException.
Гірські кордони

@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.
Сторо

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

2
Це буде спрацьовувати, але логічно, коли ви чекаєте відповіді сервера, і ви просто виконували операцію db, то це має відображати правильні зміни у вашій діяльності. Я написав блог про це, дивіться свою відповідь.
Вікас

4
Як зазначалося в коментарях до прийнятої відповіді, не потрібно створювати власний runningпрапор. AsyncTask має внутрішній прапор, який встановлюється, коли завдання скасовано. Замініть while (running)на while (!isCancelled()). developer.android.com/reference/android/os/… Отже, у цьому простому випадку вам не потрібно onCancelled()переосмислювати.
ToolmakerSteve

11

Просто: не використовуйте AsyncTask. AsyncTaskрозроблений для коротких операцій, які швидко закінчуються (десятки секунд), тому їх не потрібно скасовувати. "Відтворення аудіофайлів" не відповідає вимогам. Вам навіть не потрібна фонова нитка для звичайного відтворення аудіофайлів.


Ви пропонуєте використовувати звичайні Java-потоки та "скасовувати" запуск потоку за допомогою летючої булевої змінної - звичайного способу Java?
Самух

34
Без образи Майка, але це не прийнятна відповідь. У AsyncTask є метод скасування, і він повинен працювати. Наскільки я можу сказати, це не так - але навіть якщо я роблю це неправильно, тоді повинен бути правильний спосіб скасувати завдання. Метод не існував би інакше. І навіть короткі завдання можуть потребувати скасування - у мене є діяльність, коли він починається AsyncTask одразу після завантаження, і якщо користувач повернеться відразу після відкриття завдання, він побачить Force Закрити секунду пізніше, коли завдання закінчиться, але немає контексту існує для того, щоб використовувати його у своєму onPostExecute.
Ерік Мілл

10
@Klondike: Я поняття не маю, хто такий "Майк". ", але це не прийнятна відповідь" - ви вітаєтеся з вашою думкою. "У AsyncTask є метод скасування, і він повинен працювати." - скасування потоків у Java є проблемою протягом ~ 15 років. Це не має нічого спільного з Android. Що стосується вашого сценарію "Закрити силу", це можна вирішити булевою змінною, в якій ви перевіряєте, onPostExecute()чи слід продовжувати роботу.
CommonsWare

1
@Tejaswi Yerukalapudi: Більше того, що нічого не зробить автоматично. Дивіться прийняту відповідь на це питання.
CommonsWare

10
Ви повинні періодично перевіряти метод isCanceiled у своєму doInBackground на AsyncTask. Це прямо там, в документах: developer.android.com/reference/android/os/…
Крістофер Перрі

4

Єдиний спосіб зробити це - перевірити значення методу isCancegled () та зупинити відтворення, коли воно повернеться true.


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
Я виявив, що просто виклик відміни (true) для завдання асинхронізації та перевірка isCancelated () періодично спрацьовує, але залежно від того, що виконується ваше завдання, може зайняти до 60 секунд, перш ніж воно перерветься. Додавання Thread.sleep (1) дозволяє йому негайно переплутатися. (Завдання Async переходить у стан Wait і не відкидається негайно). Дякую за це
Джон Дж. Сміт

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

0

З посиланням на відповідь Янченка від 29 квітня "10: Використання підходу" поки (працює) "є акуратним, коли ваш код під" doInBackground "повинен бути виконаний кілька разів під час кожного виконання AsyncTask. Якщо ваш код у розділі "doInBackground" повинен бути виконаний лише один раз за виконання AsyncTask, загортання всього коду під "doInBackground" в циклі "while (running)" не зупинить запуск фонового коду (фоновий потік), коли Сам AsyncTask скасовується, тому що умова 'while (running)' буде оцінена лише після того, як весь код у циклі while буде виконаний хоча б один раз. Таким чином, вам слід (a.) Розбити код під "doInBackground" на кілька блоків "(під час запуску)" або (b.) Виконати численні "isCancelated"https://developer.android.com/reference/android/os/AsyncTask.html .

Для варіанту (a.) Можна таким чином змінити відповідь Янченка таким чином:

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

// ...
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.