Understanding Android AsyncTask (Executing Background Tasks)

The AsyncTask Android class lets us sort of bind background tasks to the UI thread. So using this class, you can perform background operations and then publish the results to the UI thread that updates the UI components. This way you won’t have to deal with threads, handlers, runnables, etc. directly yourself. It’s sort of a helper class around Thread and Handler.

So when should you use an AsyncTask ? Well any long running operation that may block the main thread and make the app unresponsive could be done via AsyncTask. Like downloading multiple files, or making HTTP requests to your server, decoding images, etc.

What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.

According to the documentation, AsyncTask should ideally be used for short background operations that lasts for a few seconds at most. For longer tasks, the Executor framework from the java.util.concurrent package should be used that contains classes/interfaces like Executor, ThreadPoolExecutor and FutureTask.

Creation and Implementation (Usage)

In order to use AsyncTask, you’ll have to subclass it. This is the simplest form of implementation of an AsyncTask:

// AsyncTask
class MyAsyncTask extends AsyncTask {
    @Override
    protected Object doInBackground(Object... params) {
        // Do some background work
        Log.d(TAG, "MyAsyncTask@doInBackground from another thread");

        return new Object();
    }
}

To execute the task you’ll have to instantiate it and call execute():

new MyAsyncTask().execute();

The execute() method can be called only once per AsyncTask instance, that means AsyncTask can be executed only once – just like a Thread. The example that we just saw is the most basic form of an AsyncTask implementation. Let’s now see all the important methods that you can override in a full-blown implementation:

class MyAsyncTask extends AsyncTask<Params, Progress, Result> {

    @Override
    protected void onPreExecute() {
        // Runs on the UI thread before doInBackground()
    }

    @Override
    protected Result doInBackground(Params... params) {
        // Perform an operation on a background thread
    }

    @Override
    protected void onProgressUpdate(Progress... values) {
        // Runs on UI thread after publishProgress(Progress...) is invoked
        // from doInBackground()
    }

    @Override
    protected void onPostExecute(Result result) {
        // Runs on the UI thread after doInBackground()
    }

    @Override
    protected void onCancelled(Result result) {
        // Runs on UI thread after cancel() is invoked
        // and doInBackground() has finished/returned
    }
}

As you can see (read in the comments actually) all callbacks except the doInBackground() method are executed on the UI thread. Also all of them are executed in a sequence except onProgressUpdate() which is initiated by and runs with doInBackground() concurrently. Using this onProgressUpdate() callback, the user can be notified in the user interface of how much work is done or you can deliver results in chunks rather than waiting for onPostExecute() to be called and sending everything at once.

It is very important to understand the three AsyncTask generic types:

  • Params – Type of data passed to the task upon execution, i.e., arguments passed to execute() and accepted by doInBackground().
  • Progress – Type of progress data reported by the background thread, i.e., from doInbackground() to the UI thread in onProgressUpdate().
  • Result – Type of result returned/produced by the background thread (doInBackground()) and sent to the UI thread (onPostExecute()).

The 3 dots argument is referred to as varargs (arbitrary number of arguments).

States

An AsyncTask can be in one of the following states:

  • PENDING – AsyncTask has been instantiated but execute() hansn’t been called on the instance.
  • RUNNING – execute() has been called.
  • FINISHED – The execution has been done as well as onPostExecute() or onCancelled() has been called.

The states change in the order shown above. The instance cannot switch to a backward state. Once finished a new instance has to be created for execution (cannot go back to RUNNING state). The current state can be monitored using getStatus().

Cancellation

If you want to cancel an AsyncTask operation, then the cancel() method can be invoked.

// Execute the task
AsyncTask task = new MyAsyncTask().execute();

// Cancel the task
task.cancel(true);

Calling cancel() sets a flag due to which isCancelled() will return true. So the background thread (doInBackground() method) should check for it whereever possible and just return from itself incase of true so that no time and resources are wasted doing further operations. If the argument passed to cancel() is true (not false) then the executing background thread will be interrupted. Sending an interrupt to the thread is a strict approach where any blocking methods (Thread.sleep() for instance) will be relieved and the background thread can check for Thread.isInterrupted() or catch InterruptedException thrown. Interruption is just a cancellation strategy as threads cannot be forced to terminate. The idea is to terminate as early as possible to release allocated resources, avoid further resource consumption, quicker return and reduce risk of memory leaks. So basically in your doInBackground() check for isCancelled() (between long running operations) and if possible wrap everything in a try/catch where you check for InterruptedException.

Calling cancel() ensures that onPostExecute() is never invoked. But it does invoke the onCancelled() callback on the UI thread after doInBackground() returns. Due to this, a cancelled task might take just as much time as a task without cancellation.

execute(Runnable)

The AsyncTask class has another version of its execute() method which is static and accepts a Runnable. It is just a convenience version of the other execute() (that we’ve seen before) for use with simple Runnable objects.

Sequential Execution

AsyncTask can be subclassed, instantiated and executed from any component in the application. That means multiple AsyncTasks can be in RUNNING state inside the application. However, if two different components (Activity and Service) launch (instantiate and execute) two different tasks that too from two different threads, both of them will be executed sequentially on a single application-wide worker thread.

// From Component A
LongTaskOne().execute();

// From Component B at the same time
LongTaskTwo().execute();

LongTaskOne will keep the LongTaskTwo from executing until its own execution is finished. This means background tasks could get delayed if there are just too many of them.

Concurrent Parellel Execution

To overcome the sequential nature, AsyncTask provides us with the executeOnExecutor(Executor exec, Params... params) method using which we can run multiple tasks in parellel on a pool of threads managed by AsyncTask. We can also use our own Executor (with a pool of threads) for custom behaviour. Don’t forget to read the warning section in the documentation of this method which basically says if you’re using it, do the needful to ensure thread safety.

The AsyncTask class has two static Executors that you can pass to executeOnExecutor():

  • AsyncTask.THREAD_POOL_EXECUTOR – An Executor that can be used to execute tasks in parellel on its pool of threads.
  • AsyncTask.SERIAL_EXECUTOR – An Executor that executes one task at a time serially (sequentially), hence thread-safe. This serialization is global app-wide (process-wise). In this case tasks are stored in an unbounded queue that are passed to THREAD_POOL_EXECUTOR to be executed. The task can then be executed in any thread in the executor’s pool but SERIAL_EXECUTOR makes sure only one task is passed and executed at a time, not multiple.

These two executors are global to the entire application, i.e., the pools are shared by all the AsyncTask operations across the application process. Thus, when there are too many background tasks to execute, delays could be noticed affecting the app’s performance. If this is the case then you can make use of a custom Executor which’ll have its own set of threads.

So for instance you could create a new custom Executor with a single thread operating off an unbounded queue, by calling Executor.newSingleThreadExecutor() in your Application class. Then you can reuse this instance from different Activity, Service, etc. components by passing to new LongTaskOne().execute(customExec, ...) and new LongTaskTwo.execute(customExec, ...) and so on.

Conclusion

So we looked into how AsyncTask makes it so simple to execute an operation in the background and port results to the UI thread that can just update the app’s user interface, fulfilling the user’s expectations.

However, you’ve to be smart by choosing the best solution for your problem, not just AsyncTask always. For instance if you want to perform a background task that won’t affect the UI, then you should probably just use Thread or even HandlerThread that facilitates message passing. There are times when you want a MessageQueue attached with your thread, again HandlerThread is a good candidate for that. Also there will be cases where IntentService will make the most sense over any other option. So be careful with your choices!

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download

Author: Rishabh

Rishabh is a full stack web and mobile developer from India. Follow me on Twitter.

3 thoughts on “Understanding Android AsyncTask (Executing Background Tasks)”

  1. Consider I have 5 different API calls to make in a same android Activity and wish to use AsyncTask, should I create 5 different AsyncTask classes for each API call? How to do in the best possible way in this scenario with no much of a code repetition?

  2. Good explanations, thanks.
    But the website is chaotic with all these advertising. The content of the page is moving alone, I will not come back

Leave a Reply

Your email address will not be published. Required fields are marked *