AsyncTask是Android提供的一个轻量级异步任务类,可以用来进行一些耗时操作,但是这个东西设计得很有意思,使用也得非常小心,稍有不慎就会出现一些不可预见的问题。
先列举一下都有哪些风险然后再从源码分析这些风险产生的原因,最后提出一些解决办法。
1、 AsyncTask无法重复执行
2、 多个AsyncTask请求处理时,由于某个任务卡住导致其他请求无法做出相应
3、 采用线程池的模式执行可能带来的线程池拒绝执行异常
首先,AsyncTask是不能重复执行的,这点毋庸置疑,来看看我们执行异步任务的方法吧
一开始就是状态的判断,并且很明确的告知,任务正在运行和运行过后都不支持重新运行。这在后面的代码分析中也可以看到,所有的任务都是保存在一个ArrayDeque里面的,通过offer和poll进行增加和剔除,这在设计肯定是不能支持到重复执行的了。
这样就导致了我们需要处理耗时任务哪怕是相同的任务时都必须启动多个AsyncTask来执行,这样就很可能导致第二个问题,多个AsyncTask造成的阻塞。
通常我们都是调用的AsyncTask.execute来执行的,可以看看这个方法
可以很明显的看到,其实这里执行时使用的是默认的executor来执行的,这个东西是一个SerialExecutor串行的执行器,并且可恨的是它居然是一个静态的,所以啊,无论多少个AsyncTask实例最终都是在一个串行队列中在执行,这是不可控的,谁知道中间哪个任务会不会花费太多的时间或者陷入无限循环呢。来看看这个SerialExecutor的实现。
这段代码无可争议的是任务肯定按顺序执行,但是线程不一定是一个哦,后面还是使用ThreadPoolExecutor来执行任务的这个得看它的了。大家看完肯定还是有疑问的,前面异步任务的执行只是exec.execute(mFuture);,mFuture还有mWorker是怎么跟这个SerialExecutor关联到一起的呢,这个就需要看看Java5的并发编程知识了,推荐这个博客可以看看: http://blog.youkuaiyun.com/zhangzhaokun/article/details/6615454
那么,这个怎么破呢?很简单,其实类中的executeOnExecutor这个方法是public final的,只要调用这个就可以解了,类似于new MyAsyncTask("AsyncTask").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");或者自己建立一个Executor来执行并行操作。
到这貌似没有问题,但是事情总是一波未平一波又起,据说3.0以前这个AsyncTask框架本来就是并行的,后来是为了避免并行处理的异常才又变成串行的。为什么会出现异常也是纯java的问题可以看看这个帖子的描述:http://blog.youkuaiyun.com/mylzc/article/details/6784415
综合上述,AsyncTask既不能支持重复执行又无法大量并行处理,所以在这种需求之下不能使用它来做了。还是得采用Thread(多个线程也可以的),handler加BlockingDeque自己来实现。
先列举一下都有哪些风险然后再从源码分析这些风险产生的原因,最后提出一些解决办法。
1、 AsyncTask无法重复执行
2、 多个AsyncTask请求处理时,由于某个任务卡住导致其他请求无法做出相应
3、 采用线程池的模式执行可能带来的线程池拒绝执行异常
首先,AsyncTask是不能重复执行的,这点毋庸置疑,来看看我们执行异步任务的方法吧
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;
}
一开始就是状态的判断,并且很明确的告知,任务正在运行和运行过后都不支持重新运行。这在后面的代码分析中也可以看到,所有的任务都是保存在一个ArrayDeque里面的,通过offer和poll进行增加和剔除,这在设计肯定是不能支持到重复执行的了。
这样就导致了我们需要处理耗时任务哪怕是相同的任务时都必须启动多个AsyncTask来执行,这样就很可能导致第二个问题,多个AsyncTask造成的阻塞。
通常我们都是调用的AsyncTask.execute来执行的,可以看看这个方法
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
可以很明显的看到,其实这里执行时使用的是默认的executor来执行的,这个东西是一个SerialExecutor串行的执行器,并且可恨的是它居然是一个静态的,所以啊,无论多少个AsyncTask实例最终都是在一个串行队列中在执行,这是不可控的,谁知道中间哪个任务会不会花费太多的时间或者陷入无限循环呢。来看看这个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);
}
}
}
这段代码无可争议的是任务肯定按顺序执行,但是线程不一定是一个哦,后面还是使用ThreadPoolExecutor来执行任务的这个得看它的了。大家看完肯定还是有疑问的,前面异步任务的执行只是exec.execute(mFuture);,mFuture还有mWorker是怎么跟这个SerialExecutor关联到一起的呢,这个就需要看看Java5的并发编程知识了,推荐这个博客可以看看: http://blog.youkuaiyun.com/zhangzhaokun/article/details/6615454
那么,这个怎么破呢?很简单,其实类中的executeOnExecutor这个方法是public final的,只要调用这个就可以解了,类似于new MyAsyncTask("AsyncTask").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");或者自己建立一个Executor来执行并行操作。
到这貌似没有问题,但是事情总是一波未平一波又起,据说3.0以前这个AsyncTask框架本来就是并行的,后来是为了避免并行处理的异常才又变成串行的。为什么会出现异常也是纯java的问题可以看看这个帖子的描述:http://blog.youkuaiyun.com/mylzc/article/details/6784415
综合上述,AsyncTask既不能支持重复执行又无法大量并行处理,所以在这种需求之下不能使用它来做了。还是得采用Thread(多个线程也可以的),handler加BlockingDeque自己来实现。