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 an example of Half Sync Half Async pattern.
Now lets examine how the serial executor does this. Please have a look at the portion of the code 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 we have already inserted at the end of the dequeue. This Runnable object (which is nothing but the mActive) then is executed on a thread taken from the threadpool. In that thread, then "finally "block gets executed.
Now there are two scenarios.
Another AsyncTask instance has been created and we call the execute method on it when the first task is being executed.
Execute method is called for the second time on a same instance of the AsyncTask when the first task is getting executed.
Scenario I : If we look at the execute
function of the SerialExecutor
, we will find that we actually create a new runnable thread (Say thread t) for processing the background task.
Inside that thread t, we run the run function of the mActive
. But as it is in the try block, the finally will be executed only after the background task is finished in that thread. (Remember both try and finally are happening inside the context of t).
Inside finally block, when we call the scheduleNext function, the mActive
becomes NULL because we have already emptied the queue. However, if another instance of the same AsyncTask
is created and we call execute on them, the execute function of these AsyncTask
won’t be executed because of the synchronization keyword before execute and also because the SERIAL_EXECUTOR
is a static instance (hence all the objects of the same class will share the same instance… its an example of class level locking) I mean no instance of the same AsyncTask class can preempt the execute function (and as a result the background task that is running in thread t). what it all means that there will be only one active thread running the task. This thread may not be the same for different tasks, but only one thread at a time will execute the task. hence the later tasks will be executed one after another only when the first task completes, that is why it is called SerialExecutor
.
Scenario II: In this case we will get an exception error. To understand why the execute function cannot be called more than once on the same Asynctask object, please have a look at the below code snippet taken from executeOnExecutor
in AsyncTask.java
especially in the below mentioned 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 a task is in the running status it throws an IllegalStateException saying “Cannot execute task: the task is already running.”.
You can read my discussion on AsyncTask internals
https://docs.google.com/document/d/1_zihWXAwgTAdJc013-bOLUHPMrjeUBZnDuPkzMxEEj0/edit?usp=sharing