Android AsyncTask 线程限制?

Posted

技术标签:

【中文标题】Android AsyncTask 线程限制?【英文标题】:Android AsyncTask threads limits? 【发布时间】:2012-03-28 02:09:23 【问题描述】:

我正在开发一个应用程序,每次用户登录系统时我都需要更新一些信息,我还使用手机中的数据库。对于所有这些操作(更新、从数据库检索数据等),我使用异步任务。到目前为止,我不明白为什么不应该使用它们,但最近我体验到,如果我执行一些操作,我的一些异步任务只会在预执行时停止并且不会跳转到 doInBackground。这太奇怪了,不能这样,所以我开发了另一个简单的应用程序来检查有什么问题。奇怪的是,当异步任务总数达到 5 个时,我得到相同的行为,第 6 个在预执行时停止。

android 对 Activity/App 的 asyncTasks 有限制吗?还是只是一些错误,应该报告?有没有人遇到过同样的问题,也许找到了解决方法?

代码如下:

只需创建 5 个线程以在后台工作:

private class LongAsync extends AsyncTask<String, Void, String>

    @Override
    protected void onPreExecute()
    
        Log.d("TestBug","onPreExecute");
        isRunning = true;
    

    @Override
    protected String doInBackground(String... params)
    
        Log.d("TestBug","doInBackground");
        while (isRunning)
        

        
        return null;
    

    @Override
    protected void onPostExecute(String result)
    
        Log.d("TestBug","onPostExecute");
    

然后创建这个线程。会进入preExecute并挂起(不会去doInBackground)。

private class TestBug extends AsyncTask<String, Void, String>

    @Override
    protected void onPreExecute()
    
        Log.d("TestBug","onPreExecute");

        waiting = new ProgressDialog(TestActivity.this);
        waiting.setMessage("Loading data");
        waiting.setIndeterminate(true);
        waiting.setCancelable(true);
        waiting.show();
    

    @Override
    protected String doInBackground(String... params)
    
        Log.d("TestBug","doInBackground");
        return null;
    

    @Override
    protected void onPostExecute(String result)
    
        waiting.cancel();
        Log.d("TestBug","onPostExecute");
    

【问题讨论】:

【参考方案1】:

所有 AsyncTask 都由共享(静态)ThreadPoolExecutor 和 LinkedBlockingQueue 在内部控制。当您在 AsyncTask 上调用 execute 时,ThreadPoolExecutor 将在未来某个时间准备好时执行它。

“我什么时候准备好?” ThreadPoolExecutor 的行为由两个参数控制,核心池大小最大池大小。如果当前活动的线程少于核心池大小并且有新作业进入,则执行程序将创建一个新线程并立即执行它。如果至少有核心池大小的线程在运行,它将尝试将作业排队并等待直到有空闲线程可用(即直到另一个作业完成)。如果无法将作业排队(队列可以有最大容量),它将创建一个新线程(最多为最大池大小线程)供作业运行。非核心空闲线程最终可以退役根据保活超时参数。

在 Android 1.6 之前,核心池大小为 1,最大池大小为 10。从 Android 1.6 开始,核心池大小为 5,最大池大小为 128。队列大小在这两种情况下都是 10 . keep-alive 超时时间是 2.3 之前的 10 秒,之后的 1 秒。

考虑到所有这些,现在很清楚为什么 AsyncTask 似乎只会执行 5/6 的任务。第 6 个任务正在排队等待其他任务之一完成。这是您不应该将 AsyncTasks 用于长时间运行的操作的一个很好的理由 - 它会阻止其他 AsyncTasks 运行。

为了完整起见,如果您重复练习超过 6 个任务(例如 30 个),您将看到超过 6 个将进入doInBackground,因为队列将变满并且执行程序被推送以创建更多工作线程。如果您继续执行长时间运行的任务,您应该会看到 20/30 变为活动状态,其中 10 仍在队列中。

【讨论】:

“这是一个很好的理由,为什么您不应该将 AsyncTasks 用于长时间运行的操作”您对这种情况有什么建议?手动生成新线程还是创建自己的执行器服务? 执行器基本上是线程之上的抽象,减轻了编写复杂代码来管理它们的需要。它将您的任务与它们应该如何执行分离。如果您的代码仅依赖于执行程序,那么很容易透明地更改使用了多少线程等。我真的想不出自己创建线程的充分理由,因为即使对于简单的任务,使用的工作量一个 Executor 是相同的,如果不是更少的话。 请注意,从 Android 3.0+ 开始,默认的并发 AsyncTask 数量已减少到 1。更多信息:developer.android.com/reference/android/os/… 哇,非常感谢您的出色回答。最后,我解释了为什么我的代码会如此零星和神秘地失败。 @antonyt,还有一个疑问,已取消的 AsyncTasks,它会计入 AsyncTasks 的数量吗?即,计入core pool sizemaximum pool size【参考方案2】:

@antonyt 有正确的答案,但如果您正在寻找一个简单的解决方案,那么您可以查看 Needle。

您可以使用它定义自定义线程池大小,并且与AsyncTask 不同,它在所有 Android 版本上的工作原理相同。有了它,你可以说:

Needle.onBackgroundThread().withThreadPoolSize(3).execute(new UiRelatedTask<Integer>() 
   @Override
   protected Integer doWork() 
       int result = 1+2;
       return result;
   

   @Override
   protected void thenDoUiRelatedWork(Integer result) 
       mSomeTextView.setText("result: " + result);
   
);

或类似的东西

Needle.onMainThread().execute(new Runnable() 
   @Override
   public void run() 
       // e.g. change one of the views
   
); 

它可以做得更多。查看GitHub。

【讨论】:

最后一次提交是 5 年前 :(【参考方案3】:

更新:自 API 19 起,核心线程池大小已更改以反映设备上的 CPU 数量,开始时最少 2 个,最多 4 个,同时增长到最大值CPU*2 +1 - Reference

// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;

还要注意,虽然 AsyncTask 的默认执行器是串行的(一次执行一个任务,并按照它们到达的顺序),但 method

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
        Params... params)

您可以提供一个 Executor 来运行您的任务。您可以提供 THREAD_POOL_EXECUTOR 幕后执行程序,但没有任务序列化,或者您甚至可以创建自己的执行程序并在此处提供。 但是,请仔细注意 Javadocs 中的警告。

警告:允许多个任务从线程池并行运行通常不是人们想要的,因为它们的操作顺序没有定义。例如,如果这些任务用于修改任何共同的状态(例如由于单击按钮而写入文件),则无法保证修改的顺序。如果不仔细工作,在极少数情况下,较旧版本的数据可能会覆盖较新版本的数据,从而导致难以理解的数据丢失和稳定性问题。此类更改最好​​按顺序执行;为了保证无论平台版本如何都可以序列化此类工作,您可以将此函数与 SERIAL_EXECUTOR 一起使用。

还有一点需要注意的是,框架提供的执行器 THREAD_POOL_EXECUTOR 及其串行版本 SERIAL_EXECUTOR(这是 AsyncTask 的默认设置)都是静态的(类级构造),因此在您的应用程序进程中的所有 AsyncTask 实例之间共享.

【讨论】:

以上是关于Android AsyncTask 线程限制?的主要内容,如果未能解决你的问题,请参考以下文章

Android中的线程状态 - AsyncTask详解

Android面试收集录7 AsyncTask详解

转:android异步任务设计思详解(AsyncTask)

Android 对线程封装了:AsyncTask, HandlerThread和线程池。 有知道这三个如何选择吗?

android线程与线程池-----AsyncTask《android开发艺术与探索》

Android 多线程-----AsyncTask详解