【问题标题】:How does AsyncTask SerialExecutor work?AsyncTask SerialExecutor 是如何工作的?
【发布时间】:2013-10-06 10:29:29
【问题描述】:
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);
        }
    }
}

上面的代码 sn-p 来自实现 SerialExcutor 的 AsyncTask 源代码,但我不明白它是如何工作的。

当一个新任务到来时,它被放入一个ArrayDeque的末尾,只有当当前没有其他任务正在执行时,ArrayDeque顶部的任务才会被执行。 (当 mActive == null 时)。

所以如果一个任务正在执行,当一个新任务到达时,什么都不会触发,当任务执行完成时,ArrayDeque怎么知道弹出下一个任务在顶部执行它???

【问题讨论】:

    标签: android android-asynctask


    【解决方案1】:

    任务由THREAD_POOL_EXECUTOR 在一个单独的线程上执行,该线程接受Runnable。在这个Runnable中,当正在运行的任务由于某种原因完成时,scheduleNext()finally块中被调用。如果队列中有任务,则执行第一个,否则执行器将处于空闲状态,直到下一次调用execute()。此外,synchronized 确保execute()scheduleNext() 不能同时在不同的线程中运行。

    【讨论】:

    • 谢谢@laalto...我犯了一个错误,出于某种原因,当我看到一个可运行的对象时,我一直认为它是在线程中运行的。所以在 try {r.run() } finally {scheduleNext()} 结构中,我认为 run() 立即返回,然后 scheduleNext() 以正确的方式执行。这就是我困惑的根源......
    【解决方案2】:

    让我们深入研究 SerialExecutor 类。在这个类中,我们有 final

    ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    

    这实际上作为不同线程上不同请求的序列化程序。这是一个半同步半异步模式的例子。

    现在让我们看看串行执行器是如何做到这一点的。请看一下 SerialExecutor 的代码部分,写成

    if (mActive == null) {
        scheduleNext();
    }
    

    所以当第一次在 Asynctask 上调用 execute 时,这段代码在主线程上执行(因为 mActive 将被初始化为 NULL),因此它会将我们带到 scheduleNext() 函数。 ScheduleNext()函数写成如下:

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

    所以在 schedulenext() 函数中,我们使用已在 dequeue 末尾插入的 Runnable 对象初始化 mActive。这个 Runnable 对象(它只是 mActive)然后在从线程池中取出的线程上执行。在那个线程中,“finally”块被执行。

    现在有两种情况。

    1. 已经创建了另一个 AsyncTask 实例,我们在执行第一个任务时调用它的 execute 方法。

    2. 在执行第一个任务时,在 AsyncTask 的同一实例上第二次调用 Execute 方法。

    场景一:如果我们查看SerialExecutorexecute函数,我们会发现我们实际上创建了一个新的可运行线程(比如说线程t)来处理后台任务。 在那个线程 t 中,我们运行mActive 的运行函数。但是由于它在 try 块中,finally 只会在该线程中的后台任务完成后才会执行。 (记住 try 和 finally 都发生在 t 的上下文中)。 在 finally 块中,当我们调用 scheduleNext 函数时,mActive 变为 NULL,因为我们已经清空了队列。但是,如果创建了相同AsyncTask 的另一个实例并且我们对它们调用execute,则这些AsyncTask 的execute 函数将不会执行,因为execute 之前的同步关键字以及SERIAL_EXECUTOR 是静态的实例(因此同一个类的所有对象将共享同一个实例……它是类级别锁定的一个示例)我的意思是同一个 AsyncTask 类的任何实例都不能抢占执行函数(因此,正在运行的后台任务线程 t)。这一切意味着只有一个活动线程运行该任务。对于不同的任务,这个线程可能不一样,但一次只有一个线程会执行该任务。因此,只有在第一个任务完成时,后面的任务才会一个接一个地执行,这就是为什么它被称为SerialExecutor

    场景二:在这种情况下,我们会得到一个异常错误。要了解为什么不能在同一个 Asynctask 对象上多次调用 execute 函数,请查看以下代码 sn-p 取自 executeOnExecutor in AsyncTask.java 尤其是在下面提到的部分:

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

    从上面的代码 sn-p 可以清楚地看出,如果我们在任务处于运行状态时调用执行函数两次,它会抛出 IllegalStateException 说“无法执行任务:任务已经在运行。”。

    你可以阅读我关于 AsyncTask 内部的讨论

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

    【讨论】:

      猜你喜欢
      • 2013-12-17
      • 1970-01-01
      • 1970-01-01
      • 2018-05-08
      • 1970-01-01
      • 1970-01-01
      • 2017-07-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多