我知道它使用Java Executor执行操作,但仍有一些问题我不太明白,例如:
- 在Android应用程序中可以同时启动多少个AsyncTask?
- 当我启动10个AsyncTask时,所有任务都会同时运行还是一个接一个地运行?
另外,当我启动100000个AsyncTask时,我开始收到OutOfMemoryError错误。
那么,可以同时运行的AsyncTask数量是否有限制?
注意:我已在SDK 4.0上进行了测试。
AsyncTask
有一段相当漫长的历史。
当它首次出现在Cupcake(1.5)中时,它使用一个附加线程(一个接一个)处理后台操作。 在Donut(1.6)中进行了更改,开始使用线程池。 直到池耗尽之前,可以同时处理操作。 在这种情况下,操作会被排队等待处理。
自从Honeycomb以来,默认行为已切换回使用单个工作线程(一个接一个处理)。 但是引入了新方法(executeOnExecutor),让您有可能并行运行任务(有两个不同的标准执行程序:SERIAL_EXECUTOR
和THREAD_POOL_EXECUTOR
)。
任务排队的方式也取决于您使用的执行程序。 在并行执行程序的情况下,您受限于10个任务的限制(new LinkedBlockingQueue<Runnable>(10)
)。 在串行执行程序的情况下,您没有限制(new ArrayDeque<Runnable>()
)。
因此,您的任务如何处理取决于您如何运行它们以及您在哪个SDK版本上运行它们。
至于线程限制,我们没有任何保证,但是查看ICS源代码可以发现池中的线程数在范围5..128
内变化。
当您使用默认的execute
方法启动100000个任务时,将使用串行执行程序。 由于无法立即处理的任务会排队等待处理,因此会出现OutOfMemoryError
(成千上万的任务被添加到数组支持的队列中)。
可以一次启动的确切任务数取决于您正在运行的设备的内存类别以及再次您使用的执行程序。
LinkedBlockingQueue
在 Android KitKat 中作为 THREAD_POOL_EXECUTOR
的容量现在是 128。ArrayDeque
作为 SERIAL_EXECUTOR
仍然没有容量限制。 - Jeff Lockhart 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>(10);
/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
第一个是ThreadFactory,其负责创建工作线程。该类的成员变量是到目前为止创建的线程数。当它创建一个工作线程时,这个数字就会增加1。
接下来是BlockingQueue。从Java的blockingqueue文档中可以知道,它实际上提供了一个线程安全的同步队列,实现先进先出的逻辑。
接下来是线程池执行器,它负责创建一个工作线程池,可以在需要时调用不同的任务来执行。
如果我们看一下前几行,我们就会知道Android将最大线程数限制为128(如private static final int MAXIMUM_POOL_SIZE = 128所示)。
现在下一个重要的类是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);
}
and
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
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)");
}
}
mStatus = Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
从上述代码中可以看出,我们可以从Asynctask的exec函数中调用executeOnExecutor,并且在这种情况下它会使用默认执行程序。如果我们深入研究Asynctask的源代码,我们会发现这个默认执行程序实际上只是一个串行执行程序,其代码如上所示。
现在让我们深入了解SerialExecutor类。在这个类中,我们有最终的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);
}
}
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
mTasks.offer(new Runnable)
可以看出,每次调用 execute 函数都会创建一个新的工作线程。现在您可能已经能够发现 Half Sync - Half Async 模式与 SerialExecutor 的功能之间的相似之处了。然而,让我澄清一下疑惑。就像 Half Sync - Half Async 模式的异步层一样,SerialExecutor 也具有将任务提交到队列中并在后台线程上执行的功能。mTasks.offer(new Runnable() {
....
}
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)");
}
}
从上面的代码片段可以看出,如果在任务处于运行状态时调用execute函数两次,它会抛出一个IllegalStateException,显示“无法执行任务:任务已在运行中。”。
如果我们希望多个任务并行执行,我们需要调用execOnExecutor并传递Asynctask.THREAD_POOL_EXECUTOR(或者可能是用户定义的THREAD_POOL作为exec参数)。
你可以在这里阅读我关于Asynctask内部的讨论。
doInBackground()
方法,但其余的任务将在队列中等待空闲的工作线程。当前5个任务中的一个完成并释放了工作线程时,队列中的一个任务将开始执行。在这种情况下,最多可以同时运行5个任务。task.executeOnExecutor(Executor exec, Params... params)
允许使用自定义线程池执行器,并配置延迟任务队列的大小。 - kapandron
AsyncTask
由容量为10的LinkedBlockingQueue
支持(在ICS和gingerbread中)。因此,它实际上取决于您要启动多少任务以及它们需要多长时间才能完成 - 但肯定有可能耗尽队列的容量。
同样,这取决于平台。在gingerbread和ICS中,最大池大小均为128 - 但是默认行为在2.3和4.0之间发生了变化 - 从默认并行更改为串行。如果您想在ICS上并行执行,则需要调用[executeOnExecutor][1]与THREAD_POOL_EXECUTOR
一起使用。
尝试切换到并行执行器并向其发送75,000个任务 - 串行实现具有没有上限的内部ArrayDeque
(当然除了OutOfMemoryExceptions)。