【问题标题】:Android AsyncTask threads limits?Android AsyncTask 线程限制?
【发布时间】: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");
    }
}

【问题讨论】:

    标签: android multithreading android-asynctask


    【解决方案1】:

    所有 AsyncTask 都由共享(静态)ThreadPoolExecutorLinkedBlockingQueue 在内部控制。当您在 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 实例之间共享.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-04-18
      • 1970-01-01
      • 1970-01-01
      • 2014-06-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-19
      相关资源
      最近更新 更多