Friday, March 18, 2011

Android thread constructs (Part 3) : AsyncTask

Similar to IntentService, AsyncTask allows us to run long running tasks in the background. However, unlike IntentService the implementation is not straight forward. One of the reason people might think is that it involves generics ! There is no need to be afraid about those really. We just need to know 2 important things. One is the type of data that will be moving around and secondly these types cannot be primitive types like int, float, boolean etc... Before going further into the implementation, let me try to explain at a conceptual level.

Concept:

You have a long task to do. You extend the AsycTask class and somewhere in there, specify your task. Then, from the main thread, create an instance of your extended class and call the execute() method. This should somehow result in a background thread executing your task. Pretty simple !

Now, there are 3 kinds of data that you will be dealing with:

1) Params : The parameters you pass on to the background task

2) Progress : The data units which the background task will be passing to you (UI thread) reporting its progress.

3) Result : The data that will be returned by the background task (to the UI thread) on completion.

Clearly there seems to be a communication going on between the UI thread and the worker thread. Hence, I like to visualize the AsyncTask as follows:

All the methods mentioned in the diagram will be a part of the extended AsyncTask class but however will run on different threads as indicated.

1) You have an instance of the AsyncTask and will call the execute() method. This method runs on the UI thread and serves as a trigger to the platform to spawn a worker thread.

2) Before handling the control to the worker thread, the platform will execute the onPreExecute() method on the main thread. We can perform any necessary task setup.

3) After this method finishes, the platform executes the doInBackground() method on the worker thread.

4,5) Now, whenever the worker thread calls the publishProgress() method, the platform calls the onProgressUpdate() method on the main thread with the parameters passed to the publishProgress() method.

6,7) Finally when the long running task is over i.e. the doInBackground() method returns, the return value is passed to the onPostExecute() method.

And, for all this to happen our AsyncTask class needs to know the datatypes for the 3 kinds of data discussed above.

It is important to note that just like IntentService, AsycTask also spawns a SINGLE worker thread and hence only one task can be run in background. Furthermore, once an instance of our AsyncTask is created, you can call the execute() method on it only ONCE. An exception will be thrown if you try to call execute() on the same instance of AsyncTask more than once. Hence, with AsyncTask if you want to run multiple tasks in background, create multiple instances and call execute on them.

Note: API level 11 (Honeycomb) adds the executeOnExecutor() method which enables to run multiple tasks on a pool of threads managed by AsyncTask class. I have not studied and tried this. I will update the post (or write a new one) once I try this out.

Implementation details:

Extend the AsyncTask class and pass to it the datatypes for Params, Progress and Result

class TestAsyncTask extends AsyncTask<String, Integer, Integer> {}

So, here, we are telling that our worker thread will accept String parameters as input, the worker thread may publish the progress to the main thread as an Integer value and finally the return value of the doInBackground method will be an Integer. Note that the Integer class is used instead of primitive data type int.

Now, It may happen that, you have a worker thread that doesn't need any input, doesn't wish to convey any progress or any result. In that case, the AsyncTask definition will look like:

class TestAsyncTask extends AsyncTask<Void, Void, Void>{}

Okay. Sticking with the first example, lets add in the doInBackground, onProgressUpdate and onPostExecute methods.

class TestAsyncTask extends AsyncTask<String, Integer, Integer> {

protected Integer doInBackground(String... strings) {

// Long running task - say processing of each passed string

for (int i = 0; i < strings.length; i++){

// Do processing of strings[i]

publishProgress(i); // publish that ith string is processed

}

return 0;

}

protected void onProgressUpdate(Integer... item){

Log.d("TestP", item[0] + " item has been processed");

}

protected void onPostExecute(Integer result){

Log.d("TestP", "AsyncTask returned : " + result);

}

}

From the main thread (say from your Activity or service), create an instance of the AsyncTask.

As mentioned before, if you want to run multiple AsyncTasks in parallel, just create another instance and call execute on it.

TestAsyncTask myATask2 = new TestAsyncTask();

myATask2.execute("five", "six", "seven", "eight");

There are 4 threading rules we need to follow for AsyncTask to work properly. These are mentioned in the AsyncTask documentation, but I'll mention those again so that everything is available at one place.

1. The task instance needs to be created on the main thread.

2. The execute() method needs to be called on the main thread.

3. Do not explicitly call any of the preExecute(), postExecute(), doInBackground() and onProgressUpdate() methods.

4. The task can be executed only once on one instance.

Thats all you need to know to execute AsyncTask.
I'm pretty much excited about my next post. I'm planning to do a comparison of various background mechanisms that we have seen till now as well as try to come up with some guidelines. I'm still working on it, hope to post them soon. Stay tuned !