why asynctask is not suitable for long running operation

AsyncTask在Android的开发中是比较常用的一个类,AsyncTask提供的接口使我们能够很方便的将任务放到背景线程执行,并在任务执行的过程中及得到执行后,将结果回传给UI线程。但是Android中multiple thread 以及AsyncTask的使用却有许多值得注意的地方。

####AsyncTask不适合耗时较长的任务的原因####
虽然AsyncTask非常易用,但是在Android的开发文档中,明确表明了AsyncTasks should ideally be used for short operations (a few seconds at the most.)。为什么会有这么一条限制?通过阅读AsyncTask的源代码我们发现,AsyncTask的底层实现就是利用了Thread 和 Handler方式:

  • 首先AsyncTask会在线程池中得到一个Thread,并将任务在Thread中执行
  • 任务执行完毕后,Handler会将执行的结果返回给UI Thread的message loop,从而通知UI改变

这种执行方式并不应该限制任务的执行时间,因此AsyncTask不适用于长时间的工作的原因并不在与AsyncTask本身的实现机制

下面我们看一下AsyncTask具有这个局限的根本原因。

在Activity中使用AsyncTask的基本流程如下:

  • 在Activity中创建一个继承自AsyncTask的内部类,之所以采用内部类的实现,是为了方便的将结果返回给Activity时,我们能够很方便的得到Activity中定义的变量
  • 在Activity中实例化一个AsyncTask变量,开始在背景线程执行工作。

这种工作流程实际上存在一些问题,主要如下:

  • 如果AsyncTask执行的是长时间的任务,那么如果其所依赖的Activity失效了,那么AsyncTask依然会继续在背景线程工作,而却不能将执行的结果返回给UI线程了。比方说,有一下的事件流程:

      1. AsyncTask在Activity A中启动

      2. 用户旋转了Device,从Portrait模式转到Landscape模式

      3. AsyncTask任务执行完毕,需要更新 Activity A中的View

    由于在Device Rotation过程中,之前的Activity A会被系统删除,并以一个新的Activity代替,因此在执行到3时,AsyncTask无法更新view,并且浪费了系统的计算资源

  • 由于我们AsyncTask的子类是Activity的内部类,而根据Java的语法,内部类的实例会保持一份对其外部类对象的引用,所以在上面的情形中,不仅浪费系统的资源,做了无意义的计算,而且AsyncTask会使Activity A无法被GC及时的回收,从而引起内存泄露

个人以为,这种情况就是不建议AsyncTask用来执行长时间任务的原因。至于对于耗时任务的处理,Android提供了Service 与 Loader, 他们不必依赖于一个Activity 或者 Fragment,因此不会出现上述情况。

除了上面的考虑之外,默认的AsyncTask使用的线程池最大线程数为5,队列大小为10,这意味着在asyncTask的数目大于15个之前,一直只有5个线程可以并发执行,因此如果有的asyncTask执行时间过长,会出现某些task的饿死现象。

####AsyncTask任务执行方式的变换####
前面我们说过,AsyncTask的任务是放在线程池中执行的,但是由于Android版本对AsyncTask实现的改变,这种说法并不完全正确,因此我们在使用中也需注意:

  • 在API Level4 之前,所有AsyncTask的任务是在一个背景线程中顺序执行的,也就是说,多个AsyncTask之间是不能并行的。
  • 从API Level4 到API Level13, 所有AsyncTask的任务随机分配到一个线程池中执行,由于线程池中有多个线程,因此多个AsyncTask之间是可以并行的。
  • 从API Level13开始,默认情况下(如果我们调用了AsyncTask.execute()), 那么多个AsyncTask是在同一个背景线程中顺序执行的;如果想让他们能够并行执行,需要调用AsyncTask.executeOnExecutor方法。

####Android对背景线程的处理####
Android继承了Linux系统对线程的处理方式,它仍然采用Priority 和 control group来管理线程的执行。

在Android中,UI线程的优先级应该会高于Background线程,但是紧紧通过优先级的保证,仍然不能保证UI线程得到足够多的CPU时间。比方说,现在系统中有20个background线程,那么即使背景线程的优先级低,但是由于数量比较多,仍然会导致UI 线程没有足够的CPU时间。因此,Android中还使用了Control Group的机制,即所有的background线程属于同一个control group,而UI 线程属于另一个control group。Background 线程所在的control group所能够获得的总的cpu时间收到了限制,从而保证了UI Thread能够有估计的计算资源。

如果我们使用的Thread是由UI Thread中创建出来,那么它会继承UI Thread的优先级设置,因此如果必要的话,需要在自定义线程运行之前将其优先级设置为background thread级别。

####AsyncTask应该注意的使用方式####
除了不应该用于长时间的任务外,AsyncTask还有以下需要注意:

  • 在doInBackground()中,不应该出现对Activity的引用,愿意比较明显,因为activity可能因为设备旋转等原因被释放掉。
  • 在onPostExecute()和onProgressUpdate()当中,可以出现对activity的引用。因为configuration change出现后,旧的activity被释放,到新的activity被创建的整个过程都是在main thread的message queue中进行的,而且是‘原子性过程’(过程不存在中断),而onPostExecute()和onProgressUpdate()也同样是在main thread的message queue中执行,所以在其中访问activity是安全的。

这些都是自己平时使用中的思考和总结的结果,如有不正确之处,欢迎讨论指出