- Asynchronous Android Programming(Second Edition)
- Helder Vasconcelos
- 870字
- 2021-07-14 10:43:16
Controlling the level of concurrency
So far, we've carefully avoided being too specific about what exactly happens when we invoke the AsyncTask
execute method. We know that doInBackground()
will execute off the main thread, but what exactly does that mean?
The original goal of AsyncTask
was created to help developers avoid blocking the main thread. In its initial form at API level 3, AsyncTask
s were queued and executed serially (that is, one after the other) on a single background thread, guaranteeing that they would complete in the order they were started.
This changed in API level 4 to use a pool of up to 128 threads to execute multiple AsyncTask
s concurrently with each other—a level of concurrency of up to 128. At first glance, this seems like a good thing, since a common use case for AsyncTask
is to perform blocking I/O, where the thread spends much of its time idly waiting for data.
However, as we saw in Chapter 1, Building Responsive Android Applications, there are many issues that commonly arise in concurrent programming, and indeed, the Android team realized that by executing AsyncTask
s concurrently by default, they were exposing developers to potential programming problems (for example, when executed concurrently, there are no guarantees that AsyncTask
s will complete in the same order they were started).
As a result, a further change was made at API level 11, switching back to serial execution by default, and introducing a new method that gives concurrency control back to the app developer:
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params)
From API level 11 onwards, we can start AsyncTasks with executeOnExecutor
, and in doing so, choose the level of concurrency for ourselves by supplying an Executor object.
Executor is an interface from the java.util.concurrent
package of the JDK, as described in more detail in Executor
may run tasks sequentially using a single thread, use a limited pool of threads to control the level of concurrency, or even directly create a new thread for each task.
The AsyncTask
class provides two Executor instances that allow you to choose between the concurrency levels described earlier in this section:
SERIAL_EXECUTOR
: This Executor queues tasks and makes sure that the tasks are executed by the AsyncTask ThreadPool sequentially, in the order they were submitted.THREAD_POOL_EXECUTOR
: ThisExecutor
runs tasks using a pool of threads for efficiency (starting a new thread comes with some overhead cost that can be avoided through pooling and reuse).THREAD_POOL_EXECUTOR
is an instance of the JDK classThreadPoolExecutor
, which uses a pool of threads that grows and shrinks with demand. In the case ofAsyncTask
, the pool is configured to maintain at least five threads, and expands up to 128 threads. In Android Lollipop 5.0 (API Level 21), the maximum number of threads was reduced to the number of CPU cores * 2 + 1 and theThreadPool
global enqueuing capacity was increased.
To execute AsyncTask
using a specific executor, we invoke the executeOnExecutor
method, supplying a reference to the executor we want to use, for example:
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
As the default behavior of execute since API level 11 is to run AsyncTasks serially on a single background thread, the following two statements are equivalent:
task.execute(params); task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, params);
In the next image we will show the differences between the serial executor and thread pool when either executors process a group of AsyncTask
that were enqueued sequentially:
new SleepAsyncTask(1).execute(1000); ... new SleepAsyncTask(4).execute(1000);

As shown in the preceding image, the serial executor uses the threads available in the AsyncTask
Thread Pool, however they will only process the next AsyncTask
when the previous AsyncTask
finishes. Alternatively, ThreadPoolExecutor
will start processing the next task as soon as it has a thread available to do the job without guaranteeing that they would complete in the order they were started:
Note
It is important to mention that all the AsyncTasks
from the system will share the same static executor AsyncTask.THREAD_POOL_EXECUTOR
. For the SerialExecutor
the situation is worse because if an AsyncTask
is occupying the single executor for a long period of time the next tasks will wait on a queue to get processed.
Besides the default executors provided by AsyncTask
and the ones that are available on the java.util.concurrent
, we can choose to create our own. For example, we might want to allow some concurrency by operating off a small pool of threads, and allow many tasks to be queued if all threads are currently busy.
This is easily achieved by configuring our own instance of ThreadPoolExecutor
as a static member of one of our own classes—for example, our Activity
class. Here's how we might configure an executor with a pool of four to eight threads and an effectively infinite queue:
private static final Queue<Runnable> QUEUE = new LinkedBlockingQueue<Runnable>(); public static final Executor MY_EXECUTOR = new ThreadPoolExecutor(4, 8, 1, TimeUnit.MINUTES, QUEUE);
The parameters to the constructor indicate the core pool size (4), the maximum pool size (8), the time for which idle additional threads may live in the pool before being removed (1), the unit of time (minutes), and the queue to append work when the pool threads are occupied.
Using our own Executor is then as simple as invoking our AsyncTask
as follows:
task.executeOnExecutor(MY_EXECUTOR, params);
- Node.js 10實戰
- Microsoft Exchange Server PowerShell Cookbook(Third Edition)
- Getting started with Google Guava
- Python從入門到精通(精粹版)
- Android 7編程入門經典:使用Android Studio 2(第4版)
- concrete5 Cookbook
- 零基礎Java學習筆記
- ASP.NET程序開發范例寶典
- Vue.js應用測試
- Beginning C++ Game Programming
- Apache Camel Developer's Cookbook
- 從0到1:HTML5 Canvas動畫開發
- Java 9 with JShell
- 深入理解MySQL主從原理
- 流程讓管理更高效:流程管理全套方案制作、設計與優化