130

In my Activity I use a class which extends from AsyncTask and a parameter which is an instance of that AsyncTask. When I call mInstanceOfAT.execute("") everything is fine. But the app crash when I press an update button which calls again the AsyncTask(In case the network job didnt work). Cause then appears an Exception which says

Cannot execute task: the task has already been executed (a task can be executed only once)

I have tried calling cancel(true) for the instance of the Asyctask, but it doesnt work either. The only solution so far it's to create new instances of the Asyntask. Is that the correct way?

Thanks.

Dayerman
  • 3,973
  • 6
  • 38
  • 54

5 Answers5

219

AsyncTask instances can only be used one time.

Instead, just call your task like new MyAsyncTask().execute("");

From the AsyncTask API docs:

Threading rules

There are a few threading rules that must be followed for this class to work properly:

  • The task instance must be created on the UI thread.
  • execute(Params...) must be invoked on the UI thread.
  • Do not call onPreExecute(), onPostExecute(Result), doInBackground(Params...), onProgressUpdate(Progress...) manually.
  • The task can be executed only once (an exception will be thrown if a second execution is attempted.)
Community
  • 1
  • 1
Steve Prentice
  • 23,230
  • 11
  • 54
  • 55
  • 2
    That what I said I have done, is that the only possibility? Cause I want to save memmory, instead of creating an new object. – Dayerman Jun 16 '11 at 14:51
  • 1
    Also see http://stackoverflow.com/questions/2711183/asynctask-threading-rule-can-it-really-only-be-used-once – Steve Prentice Jun 16 '11 at 14:54
  • @StevePrentice: if I create an instance of the task every x seconds with new task().execute(param), to send data to a server, how garbage collector can release memory when execute is completed? – Ant4res Mar 21 '13 at 11:17
  • 3
    @Ant4res, As long as you aren't referencing the async task instance, the GC will release the memory. However, if you have an ongoing background task, you could consider doing it in a loop inside of doInBackground and make calls to publishProgress to update the progress. Or, another approach would be to put your task into a background thread. Lots of different approaches here, but can't recommend one over another without details. – Steve Prentice Mar 21 '13 at 14:20
29

The reasons for fire-and-forget instances of ASyncTask are detailed pretty well in Steve Prentice's answer - However, whilst you are restricted on how many times you execute an ASyncTask, you are free to do what you like whilst the thread is running...

Put your executable code inside a loop within doInBackground() and use a concurrent lock to trigger each execution. You can retrieve the results using publishProgress()/onProgressUpdate().

Example:

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

Of course, this is slightly more complicated than the traditional usage of ASyncTask, and you give up the use of publishProgress() for actual progress reporting. But if memory is your concern, then this approach will ensure only one ASyncTask remains in the heap at runtime.

seanhodges
  • 17,426
  • 15
  • 71
  • 93
  • But the point is that I dont want to reExecute the Asyntask while is running, but in cause that this one has finish and hasnt receive the data as it should, then call it again. – Dayerman Jun 16 '11 at 15:25
  • Actually you only execute the ASyncTask once this way, and you can check if the data is correct inside the onPublishProgress method (or delegate the check somewhere else). I used this pattern for a similar problem a while back (lots of tasks being fired in quick succession, risking the heap size). – seanhodges Jun 16 '11 at 15:57
  • But what about if in that moment the server doesnt response, and I want to try again 10 sec later? The AsyncTask has finished already, rigth?Then I need to call it again – Dayerman Jun 16 '11 at 16:01
  • I've added some example code to describe what I mean. ASyncTask will only finish once you're happy with the result and call "terminateTask()". – seanhodges Jun 17 '11 at 08:47
  • @seanhodges I am facing some issue while implementing this solution. I create new thread [here](http://stackoverflow.com/q/42645356/5332855) Please provide some solution to handle that exception. – priyanka kamthe Mar 07 '17 at 10:12
  • 1
    If you get `IllegalMonitorStateException` in `runAgain` (called by `onProgressUpdate` ) see this answer: https://stackoverflow.com/a/42646476/2711811. It suggests (and worked for me) that the `signal()` needs to be surrounded by a `lock`/`unlock`. This may have to do with the timing of `publishProgress` calling `onProgressUpdate`. –  Nov 10 '18 at 16:50
2

I had the same issue. In my case i have a task i want to do in onCreate() and in onResume(). So i made my Asynctask static, and get the instance from it. Now we still have the same problem.

So what i did in the onPostExecute() is this:

instance = null;

Keeping in mind that i check in the static getInstance method that my instance isn't null, else i create it:

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

The method in postExecute will empty the instance and recreate it. Of course this can be done outside the class.

SamuelD
  • 333
  • 2
  • 10
1

I have made my rotation tasks static, which then helped me attach, detach, and reattach them to UI threads on rotation changes. But to go back to your question, what I do is create a flag to see if the thread is running. When you want to restart the thread I check if the rotation task is running if it is I toast a warning. If it is not, I make it null and then create a new one, which will work around the error you are seeing. Furthermore, upon successful completion I null out the completed rotation aware task so that it is ready to go again.

ControlAltDelete
  • 3,576
  • 5
  • 32
  • 50
0

Yes it is true, the doc says that only can be executed one Asyntask.

Every time that you need to use it you have to instance:

// 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.
  • 1,706
  • 22
  • 22