Android's AsyncTask helps in abstracting the need to deal with thread pools, thread factory, minimum number of threads in the pool etc. However, any internal failures isn't really handled well or translated into an AsyncTask terminology. One such failure is the RejectedExecutionException which isn't even mentioned in the official documentation.
So when and why is this exception thrown and how are application developers supposed to handle? AsyncTask's execute API basically executes a Runnable object on an Executor. Developers could either use a default executor or AsyncTask.THREAD_POOL_EXECUTOR or specify custom implementations.
AsyncTask.THREAD_POOL_EXECUTOR is meant to be used as a performance optimization where multiple asynchronous tasks could be executed in parallel. This is however limited by the minimum, maximum number of threads in the pool and a queue size.
public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
sPoolWorkQueue is the queue meant to hold tasks that can't be executed immediately. The executor would eventually dequeue tasks as existing pool threads become free.
private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128);
The queue is defined to hold a maximum of 128 tasks. So what happens when the application spams the AsyncTask executeOnExecutor API? In the worst case, the enqueue outpaces dequeue and the ThreadPoolExecutor throws a RejectedExecutionException. Ideally, a maximum limit of 128 asynchronous requests seems reasonable. However, it is still possible to reach this threshold in a well designed application. It could happen when the application is designed to launch a number of tasks and cancel them when the activity is paused etc. The problem is with the implementation of the AsyncTask.cancel API.
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}
The implementation doesn't remove pending requests from the queue instead just marks them as cancelled. It however ensures that active executions are interrupted, if requested. Apart from that, canceled pending tasks are actually scheduled and they just bail out without invoking AsyncTask.doInBackground(...). This logic breaks applications which tend to post tasks quickly and cancels them upon an event.
ThreadPoolExecutor has a remove(Runnable) API for this reason and gives an ability for efficient queue management. Sadly, this can't be fixed in custom extensions of the AsyncTask (even when used with a custom thread pool executor) as the Runnable instance (mFuture) is internal. This has to be fixed in Android's framework source with a simple patch. Application developers meanwhile will have to fork and have their own version of ASyncTask.
So when and why is this exception thrown and how are application developers supposed to handle? AsyncTask's execute API basically executes a Runnable object on an Executor. Developers could either use a default executor or AsyncTask.THREAD_POOL_EXECUTOR or specify custom implementations.
AsyncTask.THREAD_POOL_EXECUTOR is meant to be used as a performance optimization where multiple asynchronous tasks could be executed in parallel. This is however limited by the minimum, maximum number of threads in the pool and a queue size.
public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
sPoolWorkQueue is the queue meant to hold tasks that can't be executed immediately. The executor would eventually dequeue tasks as existing pool threads become free.
private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128);
The queue is defined to hold a maximum of 128 tasks. So what happens when the application spams the AsyncTask executeOnExecutor API? In the worst case, the enqueue outpaces dequeue and the ThreadPoolExecutor throws a RejectedExecutionException. Ideally, a maximum limit of 128 asynchronous requests seems reasonable. However, it is still possible to reach this threshold in a well designed application. It could happen when the application is designed to launch a number of tasks and cancel them when the activity is paused etc. The problem is with the implementation of the AsyncTask.cancel API.
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}
The implementation doesn't remove pending requests from the queue instead just marks them as cancelled. It however ensures that active executions are interrupted, if requested. Apart from that, canceled pending tasks are actually scheduled and they just bail out without invoking AsyncTask.doInBackground(...). This logic breaks applications which tend to post tasks quickly and cancels them upon an event.
ThreadPoolExecutor has a remove(Runnable) API for this reason and gives an ability for efficient queue management. Sadly, this can't be fixed in custom extensions of the AsyncTask (even when used with a custom thread pool executor) as the Runnable instance (mFuture) is internal. This has to be fixed in Android's framework source with a simple patch. Application developers meanwhile will have to fork and have their own version of ASyncTask.
No comments:
Post a Comment