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

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

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

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

4

2 回答 2

6

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

于 2013-10-06T10:44:56.013 回答
0

让我们深入研究 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 方法。

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

场景二:在这种情况下,我们会得到一个异常错误。要了解为什么不能在同一个 Asynctask 对象上多次调用执行函数,请查看以下代码片段executeOnExecutorAsyncTask.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)");
            }
        }

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

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

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

于 2013-11-26T09:15:07.847 回答