Source code parsing - AsyncTask sDefaultExecutor parsing

Posted by davidppppppppppp on Sat, 27 Jul 2019 11:33:24 +0200

We looked at the execution process of AsyncTask together in the previous blog post, but due to length, we did not parse the default startup mode (sDefault Executor) of AsyncTask. So, in this blog post, we will parse sDefault Executor.

 

Relevant blog posts

Android Foundation - Basic Use of AsyncTask

Source Parsing-AsyncTask Source Parsing

Let's review what we wrote in the previous blog and start AsyncTask in two ways. One is to call the execute method, the other is to call the executeOnExecutor method. The execute method finally calls the executeOnExecutor method, and one of its parameters is sDefaultExecutor, which is the object we focus on.

First paste the source code

    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

    private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }


    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

After I remove some code that I don't want to shut down, the structure here is very clear.

First, in the execute method, sDefaultExecutor is passed in as the execution thread pool to execute AsyncTask, which is a static private thread pool with partial thread security of AsyncTask. Its real implementation is the following code

 private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

We said that this thread pool is static, and in AsyncTask it is not created by two, so it means that it is unique, so all uses of AsyncTask are the same object.

Let's first look at the structure of this inner class. First, it has two member variables. The type of mTasks is a queue, the type of queue stored is Runable, and the type of mActive is Runable. When we call this method, we first call this excute method, which adds a Runable to mtask, and then we know that mTask is a container that arranges the runables of each AsyncTask execution in order. After adding it, check if mActive is null, and then execute the scheduleNext method if it is null. Looking at this, we still don't understand what mActive means. Let's look at what is done in the scheduleNext method.

First scheduleNext takes a runable from mTask and assigns it to mActivie (at this point we can know that the mActive is currently executing Runable), then checks whether it is null or not, and uses THREAD_POOL_EXECUTOR to process the runable when it is not null.

Then let's go back and see what else we've done to add to mTask. Here we re-create a new Runable to execute the Runable we broke in, and then he wrapped the scheduleNext method with finally, which we know very well, no matter what is inside the try, he will be called. What does it mean here? Let's think about what it means. One calls scheduleNext after execution to reassign mActivie and then let it execute. That's not to say that AsyncTask will only have events executed at the same time. When the current thread is executed, it's left to go. The event of reason will be executed.

From here we can see that the Serial Executor, according to the latest popular saying, is a migrant worker. He saves all the threads executed in the background into his member variables, and then passes them to another thread pool in order to execute. Now that we've analyzed sDefault Executor, let's take a brief look at what another thread pool looks like.

   private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
   private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
   private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
   private static final int KEEP_ALIVE_SECONDS = 30;
   private static final ThreadFactory sThreadFactory = new ThreadFactory() {
   private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };

   private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);

   public static final Executor THREAD_POOL_EXECUTOR;

   static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

At first glance, it seems quite complicated. In fact, this is just a common custom thread pool. Most of the code is the initialization parameters of the thread pool (you can refer to here when you customize the thread pool). From the initialization parameters, although this thread pool supports multiple threads in parallel, since the previous sDefaultExecutor only gave him one runable each time, we can see from the outside that AsyncTask can only perform one task at a time.

At this point, basically, AsyncTask has been explained. The next blog post will summarize and answer AsyncTask's questions.

 

Topics: Android