串行还是并行?——记一次AsyncTask问题排查
事情起源于一个bug排查,一个AsyncTask的子类,执行的时候发现onPreExecute方法执行了,doInBackground却迟迟没有被调用。
懂AsyncTask一些表面原理的都知道,onPreExecute方法是在主线程执行,doInBackground方法是在后台线程执行,所以很明显是后台线程被卡住了执行不了,所以这就涉及到AsyncTask的原理问题了,查看出现bug的版本——Android 6.0源码可以知道,AsyncTask里面维护着两个线程池,THREAD_POOL_EXECUTOR和SERIAL_EXECUTOR,其中SERIAL_EXECUTOR是默认的线程池:
如果用AsyncTask.execute(params...)方法来执行任务,就会用到默认的线程池,即SERIAL_EXECUTOR;可以看出SERIAL_EXECUTOR会让所有的线程串行执行:
串行线程池而且由于SERIAL_EXECUTOR被声明为static,所以,同一个进程里的AsyncTask都会共享这个线程池,这就意味着,在同一个进程里,前面的线程不结束,后面的线程就会被挂起,这正是我遇到的情况。
接下来排查所有用AsyncTask.execute方法来执行任务的情况,终于找到了一个不合理的调用————在doInBackground里请求网络,一直死等response,而没有超时释放。修复了这种情况,问题就迎刃而解了。
除了这种解决前面线程不合理设计的办法,还有没有别的解决方式呢,因为有时候,我们的设计确实是让后台线程死循环,不跳出的。
当然有的,在AsyncTask设计上就考虑到了,前面说到AsyncTask里面还有一个线程池THREAD_POOL_EXECUTOR,从它的初始化参数可以看出,这是一个支持2到4个线程并行的线程池:
初始化参数所以,使用AsyncTask执行任务的时候,请使用AsyncTask.executeOnExecutor(THREAD_POOL_EXECUTOR)来让你的任务跑在并行的线程池上,避免出现并前面线程阻塞的情况。当然,如果你的CPU核心数够多,2到4个线程的并行度不满足的话,也可以自定义一个线程池来执行AsyncTask,不过这样的话,要注意自己维护这个线程池的初始化,释放等等操作了。
PS:AsyncTask是不是一开始就是被设计成这样的呢?笔者调研了一下,其实Android 1.5刚开始引入AsyncTask的时候,execute方法确实是串行执行的,类定义里面只有SERIAL_EXECUTOR线程池;到1.6版本时,改用并行线程池THREAD_POOL_EXECUTOR,再到3.0版本至今,就成了上面说的模样————定义两个线程池,但是默认用串行池。