android asynctask internals vis-a-vis half-sync half-async design pattern

9
Android Asynctask Internals vis-a-vis Half Sync Half Async pattern by Somenath Mukhopadhyay [email protected] The way Asynctask has been implemented in Android, is an apt example of Half Sync Half Async pattern described in Pattern Oriented Software Architecture or POSA2. First of all, let us try to understand what we mean by Half SyncHalf Async design pattern. Half Sync Half Async pattern is a specific way how we want to structure our threads in a multithreaded application. As the name suggests, in this pattern we divide the solution of managing multiple threads into two different specific zones one is synchronous and the other is an asynchronous zone. The main thread communicates with a thread asynchronously and this thread is responsible for queuing multiple tasks in a FIFO order. This thread then pushes these tasks on the synchronous layer to different threads taken from a thread pool. These thread pools then execute these tasks synchronously in the background. The whole process can be depicted by the following diagram.

Upload: somenath-mukhopadhyay

Post on 09-Jul-2015

427 views

Category:

Technology


0 download

DESCRIPTION

This document describes the internals of how Asynctask of Android works internally and its similarities with half-sync half-async design pattern as described in Pattern Oriented Software Architecture or POSA2.

TRANSCRIPT

Page 1: Android Asynctask Internals vis-a-vis half-sync half-async design pattern

Android Asynctask Internals vis-a-vis Half SyncHalf Async pattern

bySomenath Mukhopadhyay

[email protected]

The way Asynctask has been implemented in Android, is an apt example of Half Sync ­ HalfAsync pattern described in Pattern Oriented Software Architecture or POSA2. First of all, let ustry to understand what we mean by Half Sync­Half Async design pattern. Half Sync­ Half Asyncpattern is a specific way how we want to structure our threads in a multithreaded application. Asthe name suggests, in this pattern we divide the solution of managing multiple threads into twodifferent specific zones ­ one is synchronous and the other is an asynchronous zone. The mainthread communicates with a thread asynchronously and this thread is responsible for queuingmultiple tasks in a FIFO order. This thread then pushes these tasks on the synchronous layer todifferent threads taken from a thread pool. These thread pools then execute these taskssynchronously in the background. The whole process can be depicted by the following diagram.

Page 2: Android Asynctask Internals vis-a-vis half-sync half-async design pattern

If you are new to the terms Asynchronous and Synchronous in the conjunction of multithreadedapplication let me throw some lights on it. These can be best understood in a client­serverarchitecture perspective. A synchronous function means it will block the caller of it and will returnonly after it finishes its task. On the other hand an asynchronous function starts its task in thebackground but returns immediately to the caller. When it finishes the background task, it notifiesthe caller about it asynchronously and then the caller takes action.

The two scenarios have been depicted by the following two diagrams.

Now let us dive deep into the Android’s Asynctask.java file to understand it from a designer’sperspective and how it has nicely implemented Half Sync­Half Async design pattern in it.

In the beginning of the class few lines of codes are as follows:

private static final ThreadFactory sThreadFactory = new ThreadFactory() private final AtomicInteger mCount = new AtomicInteger(1);

Page 3: Android Asynctask Internals vis-a-vis half-sync half-async design pattern

public Thread newThread(Runnable r) return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); ;

private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(10);

/** * An @link Executor that can be used to execute tasks in parallel. */ public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);The first is a ThreadFactory which is responsible for creating worker threads. The membervariable of this class is the number of threads created so far. The moment it creates a workerthread, this number gets increased by 1.

The next is the BlockingQueue. As you know from the Java blockingqueue documentation, itactually provides a thread safe synchronized queue implementing FIFO logic.

The next is a thread pool executor which is responsible for creating a pool of worker threadswhich can be taken as and when needed to execute different tasks.

If we look at the first few lines we will know that Android has limited the maximum number ofthreads to be 128 (as evident from private static final int MAXIMUM_POOL_SIZE = 128).

Now the next important class is SerialExecutor which has been defined as follows:

private static class SerialExecutor implements Executor final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); Runnable mActive;

public synchronized void execute(final Runnable r) mTasks.offer(new Runnable() public void run() try r.run(); finally scheduleNext();

Page 4: Android Asynctask Internals vis-a-vis half-sync half-async design pattern

); if (mActive == null) scheduleNext();

protected synchronized void scheduleNext() if ((mActive = mTasks.poll()) != null) THREAD_POOL_EXECUTOR.execute(mActive);

The next important two functions in the Asynctask ispublic final AsyncTask<Params, Progress, Result> execute(Params... params) return executeOnExecutor(sDefaultExecutor, params);

and

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) if (mStatus != Status.PENDING) switch (mStatus) case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)");

mStatus = Status.RUNNING;

onPreExecute();

mWorker.mParams = params; exec.execute(mFuture);

return this;

Page 5: Android Asynctask Internals vis-a-vis half-sync half-async design pattern

AS it becomes clear from the above code we can call the executeOnExecutor from execfunction of Asynctask and in that case it takes a default executor. If we dig into the sourcecode ofAsynctask, we will find that this default executor is nothing but a serial executor, the code ofwhich has been given above.

Now lets delve into the SerialExecutor class. In this class we have final ArrayDeque<Runnable>mTasks = new ArrayDeque<Runnable>();.

This actually works as a serializer of the different requests at different threads. This is anexample of Half Sync Half Async pattern.

Now lets examine how the serial executor does this. Please have a look at the portion of thecode of the SerialExecutor which is written as if (mActive == null)

scheduleNext(); So when the execute is first called on the Asynctask, this code is executed on the main thread(as mActive will be initialized to NULL) and hence it will take us to the scheduleNext() function.The ScheduleNext() function has been written as follows: protected synchronized void scheduleNext() if ((mActive = mTasks.poll()) != null) THREAD_POOL_EXECUTOR.execute(mActive); So in the schedulenext() function we initialize the mActive with the Runnable object which wehave already inserted at the end of the dequeue. This Runnable object (which is nothing but themActive) then is executed on a thread taken from the threadpool. In that thread, then "finally"block gets executed.

Now there are two scenarios.

1. another Asynctask instance has been created and we call the execute method on it when thefirst task is being executed.

2. execute method is called for the second time on a same instance of the Asynctask when thefirst task is getting executed.

Scenario I : if we look at the execute function of the Serial Executor, we will find that we actuallycreate a new runnable thread (Say thread t) for processing the background task.

Page 6: Android Asynctask Internals vis-a-vis half-sync half-async design pattern

Look at the following code snippet­

public synchronized void execute(final Runnable r) mTasks.offer(new Runnable() public void run() try r.run(); finally scheduleNext(); );

As it becomes clear from the line mTasks.offer(new Runnable), every call to the executefunction creates a new worker thread. Now probably you are able to find out the similaritybetween the Half Sync ­ Half Async pattern and the functioning of SerialExecutor. Let me,however, clarify the doubts. Just like the Half Sync ­ Half Async pattern's Asynchronous layer,the mTasks.offer(new Runnable() ....part of the code creates a new thread the moment execute function is called and push it to thequeue (the mTasks). It is done absolutely asynchronously, as the moment it inserts the task inthe queue, the function returns. And then background thread executes the task in a synchronousmanner. So its similar to the Half Sync ­ Half Async pattern. Right?

Then inside that thread t, we run the run function of the mActive. But as it is in the try block, thefinally will be executed only after the background task is finished in that thread. (Remember bothtry and finally are happening inside the context of t). Inside finally block, when we call thescheduleNext function, the mActive becomes NULL because we have already emptied thequeue. However, if another instance of the same Asynctask is created and we call execute onthem, the execute function of these Asynctask won’t be executed because of thesynchronization keyword before execute and also because the SERIAL_EXECUTOR is a staticinstance (hence all the objects of the same class will share the same instance… its an exampleof class level locking) i mean no instance of the same Async class can preempt the backgroundtask that is running in thread t. and even if the thread is interrupted by some events, the finallyblock which again calls the scheduleNext() function will take care of it. what it all means thatthere will be only one active thread running the task. this thread may not be the same for differenttasks, but only one thread at a time will execute the task. hence the later tasks will be executedone after another only when the first task complets. thats why it is called SerialExecutor.

Page 7: Android Asynctask Internals vis-a-vis half-sync half-async design pattern

Scenario II: In this case we will get an exception error. To understand why the execute functioncannot be called more than once on the same Asynctask object, please have a look at the belowcode snippet taken from executorOnExecute function of Asynctask.java especially in the belowmentioned portion:

if (mStatus != Status.PENDING) switch (mStatus) case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); AS from the above code snippet it becomes clear that if we call execute function twice when atask is in the running status it throws an IllegalStateException saying “Cannot execute task: thetask is already running.”.

if we want multiple tasks to be executed parallely, we need to call the execOnExecutor passingAsynctask.THREAD_POOL_EXECUTOR (or maybe an user defined THREAD_POOL as theexec parameter.

Now its time for experimentation to justify whatever we have discussed so far is correct or not.Lets create an Android project. Make the minimum SDK version more than 12 say 14. And thetarget SDK version, say 17. Then lets make the two classes derived from Asynctask as follows.

public class MyAsynctaskWithDelay extends AsyncTask<String,Void,Void>

@Overrideprotected Void doInBackground(String... arg0)

// TODO Auto­generated method stubSystem.out.println(arg0[0] + "before sleeping of AsyctaskWithDelay..");try

Thread.sleep(10000); catch (InterruptedException e)

// TODO Auto­generated catch blocke.printStackTrace();

System.out.println(arg0[0] + "after sleeping of AsyctaskWithDelay..");//System.out.println(arg0[0]);

Page 8: Android Asynctask Internals vis-a-vis half-sync half-async design pattern

return null;

AND

public class MyAsynctaskWithNoDelayLoop extends AsyncTask<String,Void,Void>

@Overrideprotected Void doInBackground(String... arg0)

// TODO Auto­generated method stubSystem.out.println(arg0[0] + " The task with no delay...");//System.out.println(arg0[0]);return null;

Now in the main Acivity class, add these following lines of code.

MyAsynctaskWithDelay asynctask1 = new MyAsynctaskWithDelay(); MyAsynctaskWithDelay asynctask2 = new MyAsynctaskWithDelay(); MyAsynctaskWithNoDelayLoop asynctask3 = new MyAsynctaskWithNoDelayLoop(); MyAsynctaskWithNoDelayLoop asynctask4 = new MyAsynctaskWithNoDelayLoop();

asynctask1.execute("Asynctask 1"); asynctask2.execute("Asynctask 2"); asynctask3.execute("Asynctask 3"); asynctask4.execute("Asynctask 4");

Now if we run this application we will find that tasks are executing serially. And the output will belike the following:

12­03 23:33:08.535: I/System.out(784): Asynctask 1before sleeping of AsyctaskWithDelay..12­03 23:33:18.594: I/System.out(784): Asynctask 1after sleeping of AsyctaskWithDelay..12­03 23:33:18.595: I/System.out(784): Asynctask 2before sleeping of AsyctaskWithDelay..12­03 23:33:28.601: I/System.out(784): Asynctask 2after sleeping of AsyctaskWithDelay..12­03 23:33:28.605: I/System.out(784): Asynctask 3 The task with no delay...12­03 23:33:28.605: I/System.out(784): Asynctask 4 The task with no delay...

Page 9: Android Asynctask Internals vis-a-vis half-sync half-async design pattern

As you see from the output, the tasks are being executed in accordance to the sequence of theircalling of execute() function.

Hope this helps the Android learners.