官术网_书友最值得收藏!

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, AsyncTasks 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 AsyncTasks 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 AsyncTasks concurrently by default, they were exposing developers to potential programming problems (for example, when executed concurrently, there are no guarantees that AsyncTasks 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: This Executor 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 class ThreadPoolExecutor, which uses a pool of threads that grows and shrinks with demand. In the case of AsyncTask, 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 the ThreadPool 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);
主站蜘蛛池模板: 芒康县| 南部县| 同德县| 灵石县| 凉山| 平舆县| 景宁| 青州市| 上林县| 华蓥市| 九龙城区| 清流县| 奉节县| 兴仁县| 镇坪县| 五峰| 甘谷县| 台东县| 廊坊市| 吴旗县| 吴旗县| 佛冈县| 巩留县| 米脂县| 蓝山县| 汤原县| 南宫市| 廉江市| 鸡东县| 金塔县| 清丰县| 桓仁| 海伦市| 若尔盖县| 吴川市| 家居| 安阳市| 修武县| 巴青县| 建瓯市| 澳门|