How Asynctask Works In Android
Solution 1:
AsyncTask
has a rather long story.
When it first appeared in Cupcake (1.5) it handled background operations with a single additional thread (one by one). In Donut (1.6) it was changed, so that a pool of thread had begun to be used. And operations could be processed simultaneously until the pool had been exhausted. In such case operations were enqueued.
Since Honeycomb default behavior is switched back to use of a single worker thread (one by one processing). But the new method (executeOnExecutor) is introduced to give you a possibility to run simultaneous tasks if you wish (there two different standard executors: SERIAL_EXECUTOR
and THREAD_POOL_EXECUTOR
).
The way how tasks are enqueued also depends on what executor you use. In case of a parallel one you are restricted with a limit of 10 (new LinkedBlockingQueue<Runnable>(10)
). In case of a serial one you are not limited (new ArrayDeque<Runnable>()
).
So the way your tasks are processed depends on how you run them and what SDK version you run them on.
As for thread limits, we are not guaranteed with any, yet looking at the ICS source code we can say that number of threads in the pool can vary in range 5..128
.
When you start 100000 with default execute
method serial executor is used.
Since tasks that cannot be processed immediately are enqueued you get OutOfMemoryError
(thousands of tasks are added to the array backed queue).
Exact number of tasks you can start at once depends on the memory class of the device you are running on and, again, on executor you use.
Solution 2:
let us dive deep into the Android’s Asynctask.java file to understand it from a designer’s perspective 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:
privatestaticfinalThreadFactorysThreadFactory=newThreadFactory() {
privatefinalAtomicIntegermCount=newAtomicInteger(1);
public Thread newThread(Runnable r) {
returnnewThread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
privatestaticfinal BlockingQueue<Runnable> sPoolWorkQueue =
newLinkedBlockingQueue<Runnable>(10);
/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/publicstaticfinalExecutorTHREAD_POOL_EXECUTOR=newThreadPoolExecutor(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 member variable of this class is the number of threads created so far. The moment it creates a worker thread, this number gets increased by 1.
The next is the BlockingQueue. As you know from the Java blockingqueue documentation, it actually 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 threads which 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 of threads 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:
privatestaticclassSerialExecutorimplementsExecutor {
final ArrayDeque<Runnable> mTasks = newArrayDeque<Runnable>();
Runnable mActive;
publicsynchronizedvoidexecute(final Runnable r) {
mTasks.offer(newRunnable() {
publicvoidrun() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
protectedsynchronizedvoidscheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
The next important two functions in the Asynctask is
public 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:
thrownew IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
thrownew 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);
returnthis;
}
AS it becomes clear from the above code we can call the executeOnExecutor from exec function of Asynctask and in that case it takes a default executor. If we dig into the sourcecode of Asynctask, we will find that this default executor is nothing but a serial executor, the code of which 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 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 voidscheduleNext() {
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 Serial Executor, we will find that we actually create a new runnable thread (Say thread t) for processing the background task. Look at the following code snippet-
publicsynchronizedvoidexecute(final Runnable r) {
mTasks.offer(newRunnable() {
publicvoidrun() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
As it becomes clear from the line mTasks.offer(new Runnable)
, every call to the execute function creates a new worker thread. Now probably you are able to find out the similarity between 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(newRunnable() {
....
}
part of the code creates a new thread the moment execute function is called and push it to the queue (the mTasks). It is done absolutely asynchronously, as the moment it inserts the task in the queue, the function returns. And then background thread executes the task in a synchronous manner. 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, 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 Async class can preempt the background task that is running in thread t. and even if the thread is interrupted by some events, the finally block which again calls the scheduleNext() function will take care of it.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 complets. thats 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 executorOnExecute function of Asynctask.java especially in the below mentioned portion:
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
thrownewIllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
thrownewIllegalStateException("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.”.
if we want multiple tasks to be executed parallely, we need to call the execOnExecutor passing Asynctask.THREAD_POOL_EXECUTOR (or maybe an user defined THREAD_POOL as the exec parameter.
You can read my discussion on Asynctask internals here.
Solution 3:
AsyncTasks has a fixed size queue internally for storing delayed tasks. The queue size by default is 10. For example if you start 15 your tasks in a row, then first 5 will enter their doInBackground()
, but the rest will wait in the queue for free worker thread. As one of the first 5 finishes, and thus releases the worker thread, a task from the queue will start execution. In this case at most 5 tasks will run together.
Yes, there is limit of how many tasks can be run run at a time. So AsyncTask uses thread pool executor with limited max number of the worker threads and the delayed tasks queue use fixed size 10. Max number of worker threads is 128. If you try to execute more than 138 custom tasks your application will throw the RejectedExecutionException.
Solution 4:
- How many AsyncTask can be started at a time in an Android app?
AsyncTask
is backed by aLinkedBlockingQueue
with a capacity of 10 (in ICS and gingerbread). So it really depends on how many tasks you are trying to start & how long they take to finish - but it's definitely possible to exhaust the queue's capacity. - When I start 10 AsyncTask, will all tasks will run simultaneously or one by one?
Again, this depends on the platform. The maximum pool size is 128 in both gingerbread and ICS - but the *default behavior* changed between 2.3 and 4.0 - from parallel by default to serial. If you want to execute in parallel on ICS you need to call [executeOnExecutor][1] in conjunction with
THREAD_POOL_EXECUTOR
Try switching to the parallel executor and spam it with 75 000 tasks - the serial impl. has an internal ArrayDeque
that has no upper capacity bound (except OutOfMemoryExceptions ofc).
Post a Comment for "How Asynctask Works In Android"