109

I am running remote audio-file-fetching and audio file playback operations in a background thread using AsyncTask. A Cancellable progress bar is shown for the time the fetch operation runs.

I want to cancel/abort the AsyncTask run when the user cancels (decides against) the operation. What is the ideal way to handle such a case?

mghie
  • 32,028
  • 6
  • 87
  • 129
Samuh
  • 36,316
  • 26
  • 109
  • 116

9 Answers9

77

Just discovered that AlertDialogs's boolean cancel(...); I've been using everywhere actually does nothing. Great.
So...

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
  • 56,576
  • 33
  • 147
  • 165
  • additionally I called the cancel method on the AsyncTask – Samuh May 04 '10 at 08:48
  • 55
    instead of making a boolean flag for running , couldn't you remove it and make this while(!isCanceled())??? – confucius Feb 28 '12 at 09:06
  • 36
    From docs about onCancelled(): "Runs on the UI thread **after** cancel(boolean) is invoked and doInBackground(Object[]) has finished." This 'after' means that setting a flag in onCancelled and checking in doInBackground makes no sense. – lopek Feb 26 '13 at 00:15
  • 2
    @confucius thats right, but this way background thread doesn't get interrupted, suppose the case when uploading image, the uploading process continues in background and we didn't get onPostExecute called. – umesh Jan 21 '14 at 13:04
  • @lopek The suggestion was to use `isCancelled`, which you can call from any thread. The `onCancelled` callback is different. – Dan Hulme May 12 '14 at 11:03
  • 1
    @DanHulme I believe I referred to the code snippet provided in the answer, not to the confucius's comment (which is right). – lopek May 14 '14 at 14:43
  • @lopek I see what you mean now. Yes, you're right that the answer won't work at all. – Dan Hulme May 14 '14 at 15:16
  • 1
    I think this accepted answer does not work,keep it accepted is confusing – Charlesjean Jul 23 '14 at 14:14
  • 4
    Yes, this answer **doesn't work**. In the doInBackground, replace `while(running)` with `while(!isCancelled())` as others have said here in the comments. – matt5784 Aug 13 '15 at 00:35
  • This is answer is not correct....imagine a condition where you are not in a loop , but simply waiting for an http call to finish. At the time it entered running was true ..but once the running became false no effect on waiting and the prcess continues waiting. – Utsav Gupta May 04 '16 at 06:41
  • The method you need to implement to be aware of when it is cancelled is ```onCancelled(Void aVoid)```. – drindt Dec 02 '17 at 15:39
76

If you're doing computations:

  • You have to check isCancelled() periodically.

If you're doing a HTTP request:

  • Save the instance of your HttpGet or HttpPost somewhere (eg. a public field).
  • After calling cancel, call request.abort(). This will cause IOException be thrown inside your doInBackground.

In my case, I had a connector class which I used in various AsyncTasks. To keep it simple, I added a new abortAllRequests method to that class and called this method directly after calling cancel.

wrygiel
  • 5,080
  • 3
  • 24
  • 29
  • thank you, it works, but how to avoid the exception in this case? – begiPass Nov 01 '13 at 13:44
  • You must call `HttpGet.abort()` from a background thread or you'll get a `android.os.NetworkOnMainThreadException`. – Heath Borders Apr 29 '14 at 18:40
  • @wrygiel If you're doing an HTTP request, shouldn't `cancel(true)` interrupt the request? From documentation: `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 Feb 12 '16 at 14:30
  • `HttpURLConnection.disconnect();` – Oded Breiner May 21 '16 at 06:23
  • If you have a cpu consuming operation in AsyncTask, so you must call `cancel(true)`. I used it and it works. – S.M.Mousavi Nov 16 '16 at 12:52
  • what is `request.abort()`?? – user25 Feb 12 '17 at 01:58
20

The thing is that AsyncTask.cancel() call only calls the onCancel function in your task. This is where you want to handle the cancel request.

Here is a small task I use to trigger an update method

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;
        }
     }
BenMorel
  • 34,448
  • 50
  • 182
  • 322
DonCroco
  • 201
  • 2
  • 3
  • 2
    This will work, but logically when you are waiting for server response, and you just did db operation, then is should reflect correct changes to your activity. I wrote a blog on that, see my answer. – Vikas Aug 03 '11 at 18:17
  • 4
    As mentioned in comments on the accepted answer, there is no need to create your own `running` flag. AsyncTask has an internal flag that is set when the task has been cancelled. Replace `while (running)` with `while (!isCancelled())`. http://developer.android.com/reference/android/os/AsyncTask.html#isCancelled() So in this simple case, you don't need `onCancelled()` override. – ToolmakerSteve Aug 26 '15 at 17:38
11

Simple: don't use an AsyncTask. AsyncTask is designed for short operations that end quickly (tens of seconds) and therefore do not need to be canceled. "Audio file playback" does not qualify. You don't even need a background thread for ordinary audio file playback.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • are you suggesting we use regular java thread and "abort" the thread run using volatile boolean variable - the conventional Java way? – Samuh Apr 29 '10 at 14:57
  • I don't know about `volatile` -- I tend to use `java.util.concurrent` objects for that sort of thing. – CommonsWare Apr 29 '10 at 16:31
  • 34
    No offense Mike, but that's not an acceptable answer. AsyncTask has a cancel method, and it should work. As far as I can tell, it doesn't - but even if I'm doing it wrong, then there should be a right way to cancel a task. The method wouldn't exist otherwise. And even short tasks may need canceling - I have an Activity where it begins an AsyncTask immediately upon loading, and if the user hits back immediately after opening the task, they'll see a Force Close a second later when the task finishes but no context exists for it to use in its onPostExecute. – Eric Mill Jul 27 '10 at 03:01
  • 10
    @Klondike: I have no idea who "Mike" is. "but that's not an acceptable answer" -- you are welcome to your opinion. "AsyncTask has a cancel method, and it should work." -- canceling threads in Java has been a problem for ~15 years. It has nothing much to do with Android. With respect to your "Force Close" scenario, that can be solved by a boolean variable, which you test in `onPostExecute()` to see whether you should go ahead with the work. – CommonsWare Jul 27 '10 at 03:29
  • Yikes, can't believe I said "Mike" - my apologies on that. Canceling threads in Java may have been a problem for 15 years, but if it's not reliable there shouldn't be a method in the Android SDK that has a boolean flag that implies the thread will be actively interrupted if it's that non-deterministic. It may as well not be there. – Eric Mill Jul 30 '10 at 14:27
  • I realize I'm bringing up an old topic, but I'm using AsyncTask for network calls. There take ~5s to complete, but I'd still like the user to have an option to cancel the non-critical ones if they choose. Are you saying `AsyncTask.cancel()` method is not reliable? – Tejaswi Yerukalapudi May 02 '11 at 14:28
  • 1
    @Tejaswi Yerukalapudi: It's more that it won't do anything automatically. See the accepted answer on this question. – CommonsWare May 02 '11 at 15:06
  • 10
    You are supposed to check the isCancelled method periodically in your doInBackground on AsyncTask. It's right there in the docs: http://developer.android.com/reference/android/os/AsyncTask.html#isCancelled() – Christopher Perry Nov 17 '11 at 01:33
4

The only way to do it is by checking the value of the isCancelled() method and stopping playback when it returns true.

dbyrne
  • 59,111
  • 13
  • 86
  • 103
4

This is how I write my AsyncTask
the key point is add 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
  • 358
  • 3
  • 7
  • 1
    I found that simply calling cancel(true) on the async task and checking isCancelled() periodically does work, but depending on what your task is doing, can take up to 60 seconds before it gets interupted. Adding the Thread.sleep(1) enables it to get interupted immediately. (Async Task goes into a Wait state rather and isn't discarded immediately). Thanks for this. – John J Smith Oct 09 '14 at 19:31
0

Our global AsyncTask class variable

LongOperation LongOperationOdeme = new LongOperation();

And KEYCODE_BACK action which interrupt AsyncTask

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

It works for me.

Göksel Güren
  • 1,479
  • 13
  • 21
0

I don't like to force interrupt my async tasks with cancel(true) unnecessarily because they may have resources to be freed, such as closing sockets or file streams, writing data to the local database etc. On the other hand, I have faced situations in which the async task refuses to finish itself part of the time, for example sometimes when the main activity is being closed and I request the async task to finish from inside the activity's onPause() method. So it's not a matter of simply calling running = false. I have to go for a mixed solution: both call running = false, then giving the async task a few milliseconds to finish, and then call either cancel(false) or 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;
}

As a side result, after doInBackground() finishes, sometimes the onCancelled() method is called, and sometimes onPostExecute(). But at least the async task termination is guaranteed.

Piovezan
  • 3,215
  • 1
  • 28
  • 45
0

With reference to Yanchenko's answer on 29 April '10: Using a 'while(running)' approach is neat when your code under 'doInBackground' has to be executed multiple times during every execution of the AsyncTask. If your code under 'doInBackground' has to be executed only once per execution of the AsyncTask, wrapping all your code under 'doInBackground' in a 'while(running)' loop will not stop the background code (background thread) from running when the AsyncTask itself is cancelled, because the 'while(running)' condition will only be evaluated once all the code inside the while loop has been executed at least once. You should thus either (a.) break up your code under 'doInBackground' into multiple 'while(running)' blocks or (b.) perform numerous 'isCancelled' checks throughout your 'doInBackground' code, as explained under "Cancelling a task" at https://developer.android.com/reference/android/os/AsyncTask.html.

For option (a.) one can thus modify Yanchenko's answer as follows:

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

// ...

For option (b.) your code in 'doInBackground' will look something like this:

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

// ...