How does AsyncTask SerialExecutor work?

匿名 (未验证) 提交于 2019-12-03 01:02:01

问题:

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);         }     } } 

Above code snippet is from the AsyncTask source code implementing the SerialExcutor, But I dont understand how exactly it works.

when a new task arrives, it is put into the end of a ArrayDeque, the task on the top of the ArrayDeque get executed only when there is no other task is being executed currently. (when mActive == null).

So if a task is being executed when a new task arrive, there is nothing will be triggered, when the task finish executing, how the ArrayDeque know pop the next task on the top to execute it???

回答1:

Tasks are executed on a separate thread by THREAD_POOL_EXECUTOR that takes in a Runnable. In this Runnable, when the running task finishes for some reason, scheduleNext() is called in the finally block. If there are any tasks in the queue, the first one will be executed, otherwise the executor will be idle until the next call to execute(). Also, synchronized ensures that execute() and scheduleNext() cannot be run in separate threads at the same time.



回答2:

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 void scheduleNext() {      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.

  1. Another AsyncTask instance has been created and we call the execute method on it when the first task is being executed.

  2. 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 SerialExecutor, we will find that we actually create a new runnable thread (Say thread t) for processing the background task. 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 AsyncTask class can preempt the execute function (and as a result the background task that is running in thread t). 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 completes, that is 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 executeOnExecutor in AsyncTask.java especially in the below mentioned portion:

 if (mStatus != Status.PENDING) {             switch (mStatus) {                 case RUNNING:                     throw new IllegalStateException("Cannot execute task:"                             + " the task is already running.");                 case FINISHED:                     throw new IllegalStateException("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.”.

You can read my discussion on AsyncTask internals

https://docs.google.com/document/d/1_zihWXAwgTAdJc013-bOLUHPMrjeUBZnDuPkzMxEEj0/edit?usp=sharing



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!