多个AsynceTask无法同时运行的现象分析


问题

关于这篇博客所提到的问题是在一段再简单不过的代码中意外出现的。当时我使用了两个不同'AsyncTask'帮助我执行两个需要在后台执行任务。并且这两个'AsyncTask'几乎是同时运行的。原本会正常运行的代码,却不知道为什么出现了问题。总是有一个'AsyncTask'会迟迟不做出反应。看起来就好像多个'AsyncTask'不能同时执行任务。最糟糕的是,如果这两个'AsyncTask'的任务是存在依赖关系的。那么结果就是同时陷入了死锁状态。下面是我的部分代码。

AsyncTask1.execute();

AsyncTask2.execute();

当我这么执行代码的时候,'AsyncTask2'很久都没有任何反应。而如果'AsyncTask1'的任务需要依赖'AsyncTask2'的结果的话,那么'AsyncTask1'也会陷入无尽的等待中去。

我是怎么解决的

当务之急当然不是了解这种现象的背后原因了,而是找到一个可行的解决方案。这听起来似乎是很不负责任的,但却是最高效的措施。

这回google又一次的帮助了我,在StackOverflow中找到了应对这种现象的方法。看来,遇到这个问题的不只是我一个人啊。下面把代码贴上。

YourAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);

没错我的解决方案就是将'execute'改为了'executeOnExecutor'。就是这样简单的一小段代码的修改就解决了这个问题。但是问题在于上面的代码必须要再API11(Android3.0)之后的版本中才能使用,所以为了版本兼容,建议使用下面的代码。至于为什么会这样,我会在下面的文章中阐述。

if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR1) {
    YourAsyncTask.execute();
} else {
    YourAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}

问题出现的背后原理

到了这里当然不会就是结束语了。我还要弄清这背后到底发生了什么,否则这还有什么意义。coding的价值不就是在这吗!

根据几篇找到的文章中的提示,引起这个问题的主要原因是,在API11(Android3.0)之后google改变了'AsyncTask'内部的算法。'AsyncTaskk'的实现逻辑主要是这样的

  1. 由一个'Executor'管理后台的线程池,这些线程就是执行你后台任务的线程。
  2. 通过Handler机制实现UI线程与后台线程的数据交互。

而API11之后与API11之前的区别就在于这个'Executor'。下面代码中的'sExecutor'是API11前的'Executor'

private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final int KEEP_ALIVE = 1;

private static final BlockingQueue<Runnable> sWorkQueue =
        new LinkedBlockingQueue<Runnable>(10);

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 ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(
        CORE_POOL_SIZE,
        MAXIMUM_POOL_SIZE, 
        KEEP_ALIVE, 
        TimeUnit.SECONDS, 
        sWorkQueue, 
        sThreadFactory);

而API11之后的默认'Executor'是'SERIAL_EXECUTOR',其实现代码如下。

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 {
                    cheduleNext();
                }
            }
        );
        if (mActive == null) {
            scheduleNext();
        }
    }

    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) {
            THREAD_POOL_EXECUTOR.execute(mActive);
        
    }
}

这两个'Executor'的区别在于,'SERIAL_EXECUTOR'每次只会执行一个后台任务。而后续的任务都会暂时存储在'mTasks'这个队列当中。当有一个任务被执行完了,它才会调用方法'scheduleNext',既从'mTasks'中提取一个后台任务交给后台线程去执行。而'sExecutor'可以每次最多执行5个后台任务。所以这就是为什么我会遇到出现多个'AsyncTask'不能同时运行原因。(注:我当时跑的是API19)

解决方案的背后原理是什么

如果你注意到了,我提到的'SERIALEXECUTOR'这个'Executor'是默认的'Executor'。既然有默认的'Executor',那么就一定存在自定义的'Executor'。所以在API11中提供了'executeOnExecutor(Executor)'这个方法指定你自己的'Executor'。但是如果每次都要使用自定义的'Executor'那就太不人性化了。所以android在'AsyncTask'内部除了提供'SERIALEXECUTOR'这个'Executor',还提供了'THREADPOOLEXECUTOR'这个'Executor'。也就是上面的解决方案中使用的那个'Executor'。

但是如果你认为这个'THREADPOOLEXECUTOR'就是那个'sExecutor'的翻版你就错了。android对他进行了升级。她会根据当前设备的CPU的核心数量设置每次最多可执行的任务数量。下面是升级的代码。

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;

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>(128);

/**
  * 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);

最后的补充

上面的代码其中API11之前的代码具体使用的是API10的代码,API11以之后的代码具体使用的是API19的代码。


原文地址:https://www.cnblogs.com/zsw-1993/p/4879110.html