AsyncTask执行缓慢的原因分析

来源:互联网 发布:淘宝卖家信用度怎么看 编辑:程序博客网 时间:2024/05/21 08:05

原文:http://www.2cto.com/kf/201605/508396.html


这几天在做一个缓存网络加载的信息模块,在读取缓存并更新UI的时候用到AsyncTask,本来是想这样代码比较干净的,然后就发现问题了。

问题:

执行execute()以后,从onPreExecute()doInBackground()之间竟然等待了长达7秒,而且这个时间时长时短,然后就开始查找原因。
后来发现了AsyncTask的黑历史:

在1.6(Donut)之前:

在第一版的AsyncTask,任务是串行调度。一个任务执行完成另一个才能执行。由于串行执行任务,使用多个AsyncTask可能会带来有些问题。所以这并不是一个很好的处理异步(尤其是需要将结果作用于UI试图)操作的方法。

从1.6到2.3(Gingerbread)

后来Android团队决定让AsyncTask并行来解决1.6之前引起的问题,这个问题是解决了,新的问题又出现了。很多开发者实际上依赖于顺序执行的行为。于是很多并发的问题蜂拥而至。

3.0(Honeycomb)到现在

好吧,开发者可能并不喜欢让AsyncTask并行,于是Android团队又把AsyncTask改成了串行。当然这一次的修改并没有完全禁止AsyncTask并行。你可以通过设置executeOnExecutor(Executor)来实现多个AsyncTask并行。

上面这段话中心思想就是,现在AsyncTask是默认串行执行的,AsyncTask默认自己维护一个静态的线程池,而该线程池只允许同时执行一个线程,也就是说,你的应用中,可能有多个AsyncTask实例,而多次不管多少个AsyncTask,只要是调用execute()方法,都是共享这个默认进程池的,你的任务必须在之前的任务执行完以后,才能执行。可以理解为,默认情况下,所有的AsyncTask在一个独立于UI线程的线程中执行,任务需要排队,先execute的先执行,后面的只能等。所以才会出现楼主这样漫长的等待问题。

解决:

除了excute方法外,我们可以自己调用executeOnExecutor,如果使用executeOnExecutor方法,可以在外部自定义线程池,解决不能并发执行异步任务的问题。
例如:

executeOnExecutor(Executors.newCachedThreadPool());

这样这个AsyncTask实例就有了自己的线程池而不必使用AsyncTask默认的。

追加:

AsyncTask新增了两个预定义的线程池SERIAL_EXECUTOR 和 THREAD_POOL_EXECUTOR

其实 THREAD_POOL_EXECUTOR 并不是新增的,之前的就有,只不过之前(Android 2.3)它是AsyncTask私有的,未公开而已。THREAD_POOL_EXECUTOR 是一个corePoolSize为5的线程池,也就是说最多只有5个线程同时运行,超过5个的就要等待。所以如果使用 executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) 就跟2.3版本的 AsyncTask.execute() 效果是一样的。

SERIAL_EXECUTOR 是新增的,它的作用是保证任务执行的顺序,也就是它可以保证提交的任务确实是按照先后顺序执行的。它的内部有一个队列用来保存所提交的任务,保证当前只运行一个,这样就可以保证任务是完全按照顺序执行的,默认的execute()使用的就是这个,也就是 executeOnExecutor(AsyncTask.SERIAL_EXECUTOR) 与execute()是一样的。

最后引用大牛的建议:

虽然建议使用AsyncTask而不是使用Thread,但是AsyncTask似乎又有它的限制,这就要根据具体的需求情况而选择合适的工具,No Silver Bullet。下面是一些建议:

改善你的设计,少用异步处理

线程的开销是非常大的,同时异步处理也容易出错,难调试,难维护,所以改善你的设计,尽可能的少用异步。对于一般性的数据库查询,少量的I/O操作是没有必要启动线程的。

与主线程有交互时用AsyncTask,否则就用Thread

AsyncTask被设计出来的目的就是为了满足Android的特殊需求:非主线程不能操作(UI)组件,所以AsyncTask扩展Thread增强了与主线程的交互的能力。如果你的应用没有与主线程交互,那么就直接使用Thread就好了。

当有需要大量线程执行任务时,一定要创建线程池

线程的开销是非常大的,特别是创建一个新线程,否则就不必设计线程池之类的工具了。当需要大量线程执行任务时,一定要创建线程池,无论是使用AsyncTask还是Thread,因为使用AsyncTask它内部的线程池有数量限制,可能无法满足需求;使用Thread更是要线程池来管理,避免虚拟机创建大量的线程。比如从网络上批量下载图片,你不想一个一个的下,或者5个5个的下载,那么就创建一个CorePoolSize为10或者20的线程池,每次10个或者20个这样的下载,即满足了速度,又不至于耗费无用的性能开销去无限制的创建线程。

对于想要立即开始执行的异步任务,要么直接使用Thread,要么单独创建线程池提供给AsyncTask

默认的AsyncTask不一定会立即执行你的任务,除非你提供给他一个单独的线程池。如果不与主线程交互,直接创建一个Thread就可以了,虽然创建线程开销比较大,但如果这不是批量操作就没有问题。

Android的开发没有想像中那样简单,要多花心思和时间在代码上和测试上面,以确信程序是优质的

看完上面的建议,我发现,其实我做的这个缓存只是缓存字符串,根本用不着开线程,就直接放到UI线程里好了。


原创粉丝点击