Wednesday, May 18, 2011

AsyncTasks where you control the thread resource

In Android the AsyncTask class is designed to perform some long running task such as DB or net access in a background thread with pre and post execute tasks run on a UI thread, to update your user interface.

In theory its great, in practice it can be a trap.

The problem is that up until Honeycomb AsyncTask only has a single execute method that took no params and that the resource on which the task would execute is shared by all other AsyncTask instances. Worse still the threading resource depends on the version of Android. In Donut (1.5 and now again in the no arg execute method Honeycomb) a single thread is shared amongst all AsyncTask instances. From Eclair to Gingerbread an exhaustible thread pool provides the processing.

This means that if your code uses an AsyncTask it can be blocked by code in another AsyncTask task that might be running unbeknownst to you in a library. This was what was happening to me.

I commonly present a managed dialog before starting a long running task that requires the user to wait until completion and remove it after the task has completed. And I use to do so using AsyncTask. So I started to wonder why sometimes my wait dialogs were taking a REALLY long time to be dismissed. It turns out that a library I use was using AsnycTask (legitimately) to perform net access and update the UI in the post-execute. The problem was that if several of these tasks were started they quickly (or immediately in 1.5) exhausted the AsyncTask processing resource and my dialogs took s long time to go away.

The solution is to create an AsyncTask in which I gain more control of the processing resource available. Which is what has occured in Honeycomb where AsyncTask has gained a new execute method that takes an Executor. But my soluiton needs to work for any version of Android.

So here's my SimpleAsyncTask. It provides 2 execute methods. One takes an Executor and uses that to provide processing resources. The other constructs and starts a new Thread to execute the task.
/**
 * A simple representation of an asynchronous task that has pre and post executions that touch the UI.
 * <p>
 *     It is similar to {@link android.os.AsyncTask} except that it defines its own TaskExecutor
 *     instead of sharing one TaskExecutor between all AsyncTasks.
 * </p>
 * <p>
 *     That means that I can control the task queue and the threads executing the tasks.
 * </p>
 * User: William
 * Date: 16/05/11
 * Time: 7:46 PM
 */
public class SimpleAsyncTask {

    private final Handler handler;

    public SimpleAsyncTask() {
        this.handler = new Handler(Looper.getMainLooper());
    }

    /**
     * Executes this SimpleAsyncTask on its own Thread.
     */
    public void execute() {
        onPreExecute();
        final Runnable runnable = getRunnableToExecute();
        new Thread(runnable).start();
    }

    /**
     * Executes this SimpleAsyncTask using the supplied Executor.
     *
     * @param executor  Executor with which to execute this Task.
     */
    public void execute(Executor executor) {
        onPreExecute();
        final Runnable runnable = getRunnableToExecute();
        executor.execute(runnable);
    }

    /**
     * @return Runnable that executes the pre and post block on the MainLooper UI Thread and the background task in another thread.
     */
    private Runnable getRunnableToExecute() {
        return new Runnable() {

            @Override
            public void run() {
                doInBackground();

                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        onPostExecute();
                    }
                });
            }
        };
    }

    protected void onPreExecute() {
    }

    protected void doInBackground() {
    }

    protected void onPostExecute() {
    }
}
Note that it doesn't cater for generic arguments or passing or arguments from the doInBackground to the onPostExecute methods, IMHO that was over engineered. It is simpler to embed them as attributes in your Task subclass.

It also doesn't expose completion states, but if you need this capability it is trivial to add.

And finally  it doesn't cater for cancellation, but again this is trivial to add, and most of the work will be done in your override of doInBackground method in any case.


 Feel free to use it in any way you can.