同时运行多个AsyncTask--不可能吗?

270

我正在尝试同时运行两个AsyncTask。(平台是Android 1.5,HTC Hero。)但是,只有第一个会被执行。以下是一个简单的片段来描述我的问题:

public class AndroidJunk extends Activity {
 class PrinterTask extends AsyncTask<String, Void, Void> {
     protected Void doInBackground(String ... x) {
      while (true) {
       System.out.println(x[0]);
       try {
        Thread.sleep(1000);
       } catch (InterruptedException ie) {
        ie.printStackTrace();
       }
      }
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        new PrinterTask().execute("bar bar bar");
        new PrinterTask().execute("foo foo foo");

        System.out.println("onCreate() is done.");
    }
}

我期望的输出是:

onCreate() is done.
bar bar bar
foo foo foo
bar bar bar
foo foo foo

等等。但是我得到的是:

onCreate() is done.
bar bar bar
bar bar bar
bar bar bar

第二个AsyncTask从未被执行。如果我改变execute()语句的顺序,只有foo任务会产生输出。

我是否漏掉了一些明显的东西和/或做了一些愚蠢的事情? 是否不可能同时运行两个AsyncTasks?

编辑:我意识到手机运行Android 1.5,我相应地更新了问题描述。在运行Android 2.1的HTC Hero上,我没有这个问题。嗯...


你的代码在我这里可以运行,所以问题肯定出在其他地方。你有在LogCat视图中输入过滤器吗?;-) - mreichelt
嗯,这很奇怪。我的logcat里没有任何过滤器。你也是用1.6吗?如果是的话,你用的是哪款手机? - rodion
糟糕,刚刚意识到它正在运行(古老的)Android 1.5。 - rodion
1
我使用Android 1.6作为目标和一个Android 2.1模拟器。所以,如果问题只出现在安装有Android 1.5的HTC Hero上 - 那就算了,你没问题。;-) HTC Hero已经更新到了新的Android版本。如果有一些制造商搞砸了事情,我也不会再去烦恼它。此外,我也不再关心Android 1.5了。 - mreichelt
AsyncTask 应该用于 5 毫秒以下的短时间任务。可以考虑使用 ThreadPoolExecutor(https://developer.android.com/reference/java/util/concurrent/ThreadPoolExecutor.html)。相关帖子:https://dev59.com/wmw05IYBdhLWcg3w41wp - Ravindra babu
7个回答

442
AsyncTask使用线程池模式来运行doInBackground()中的内容。问题在于,最初(在早期的Android OS版本中),池大小只有1,这意味着一堆AsyncTasks没有并行计算。但后来他们解决了这个问题,现在大小为5,因此最多可以同时运行5个AsyncTasks。不幸的是,我不记得他们在哪个版本中改变了这一点。 更新: 以下是当前(2012-01-27)API对此的说明: 当首次引入AsyncTasks时,它们在单个后台线程上串行执行。从DONUT开始,这被更改为线程池,允许多个任务并行操作。在HONEYCOMB之后,计划将其改回单个线程,以避免由并行执行引起的常见应用程序错误。如果您真正想要并行执行,可以使用此方法的executeOnExecutor(Executor, Params...)版本,并使用THREAD_POOL_EXECUTOR;但是,请参阅那里的评论以获取有关其使用的警告。 DONUT是Android 1.6,HONEYCOMB是Android 3.0。 更新2: 请参见Mar 7 2012 at 1:27的kabuko的评论。 事实证明,在使用“允许多个任务并行操作的线程池”的API(从1.6开始,到3.0结束)中,同时运行的AsyncTasks数量取决于已经传递进行执行但尚未完成其doInBackground()的任务数量。

这是我在2.2上测试/确认的。假设您有一个自定义的AsyncTask,在doInBackground()中只睡眠一秒钟。AsyncTasks在内部使用固定大小的队列存储延迟任务。队列大小默认为10。如果您连续启动15个自定义任务,则前5个将进入其doInBackground(),但其余的将在队列中等待空闲工作线程。只要前5个任务中的任何一个完成并释放工作线程,队列中的任务就会开始执行。因此,在这种情况下,最多只能同时运行5个任务。但是,如果您连续启动16个自定义任务,则前5个将进入其doInBackground(),其余的10个将进入队列,但对于第16个,将创建一个新的工作线程,因此它将立即开始执行。因此,在这种情况下,最多只能同时运行6个任务。

有同时运行任务的限制。由于AsyncTask使用具有有限的最大工作线程数(128)和延迟任务队列具有固定大小10的线程池执行程序,如果尝试执行超过138个自定义任务,则应用程序将崩溃并显示java.util.concurrent.RejectedExecutionException

从3.0开始,API允许使用自定义线程池执行程序通过AsyncTask.executeOnExecutor(Executor exec, Params... params)方法。例如,如果默认的10不适合您,则可以配置延迟任务队列的大小。

正如 @Knossos 所提到的,有一个选项可以使用来自支持v.4库的 AsyncTaskCompat.executeParallel(task, params); 以在不烦扰API级别的情况下并行运行任务。该方法已在API级别26.0.0中停用。

更新:3

这里有一个简单的测试应用程序,可以玩转任务数量、串行和并行执行: https://github.com/vitkhudenko/test_asynctask

更新:4(感谢 @penkzhou 指出这一点)

从Android 4.4开始,AsyncTask 的行为与更新:2部分所描述的不同。有一个修复程序可以防止AsyncTask创建过多的线程。

在Android 4.4之前(API 19),AsyncTask 具有以下字段:

private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(10);

在Android 4.4(API 19)中,上述字段已更改为以下内容:
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 BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);

这个更改将队列大小增加到128个项目,并将最大线程数减少到CPU核心数*2 + 1。应用程序仍然可以提交相同数量的任务。


11
仅供参考,核心池大小为5,但最大值为128。这意味着,如果您填满了队列,就可以同时运行超过5个(最多128个)任务。 - kabuko
2
@kabuko:你说得完全正确。我在2.2上进行了调查,并确认如果已经有5个任务正在运行(核心池大小为5),那么它会开始将其余的任务放入延迟任务队列中,但是如果队列已满(最大队列大小为10),那么它会为该任务启动一个额外的工作线程,以便任务立即开始。 - Vit Khudenko
2
请更新此答案以使用新的兼容函数。例如:AsyncTaskCompat.executeParallel(task, params); - Knossos
1
@Arhimed 请更新答案,因为 AsyncTaskCompat.executeParallel(task, params); 现在已经被弃用 - Kirill Starostin
1
@KirillStarostin 感谢您指出,已更新。最初,我觉得这是多余的,所以根本不想提及它。我喜欢他们摆脱了它。 - Vit Khudenko
显示剩余8条评论

224

这允许在所有 Android 版本上使用 API 4+(Android 1.6+)进行并行执行:

@TargetApi(Build.VERSION_CODES.HONEYCOMB) // API 11
void startMyTask(AsyncTask asyncTask) {
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
        asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    else
        asyncTask.execute(params);
}

这是 Arhimed 出色回答的摘要。

请确保将API级别设置为11或更高版本,作为您的项目构建目标。在Eclipse中,打开 项目 > 属性 > Android > 项目构建目标 即可。 这不会破坏向旧API级别的向后兼容性。 不用担心,如果您意外使用了比minSdkVersion更高版本的功能,您将获得Lint错误提示。如果您确实想使用比minSdkVersion更高版本的功能,可以使用注释来抑制这些错误,但在这种情况下,需要自己注意兼容性问题。这正是上面代码片段中发生的事情。

2022年更新

请注意,AsyncTasks 已经废弃了一段时间。如果您使用的是Kotlin,建议使用 Kotlin协程。如果您依赖于在项目中使用Java,则可以使用java.util.concurrent中的Executors来替代。(来源)


3
必须将TimerTask.THREAD_POOL_EXECUTOR更改为AsynTask.THREAD_POOL_EXECUTOR,然后这个示例就可以工作。 - Ronnie
2
为了避免问题,您可以使调用通用:void startMyTask(AsyncTask<Void, ?, ?> asyncTask)(如果您的doInBackground采用Void)。 - Peter
如果你的类继承AsyncTask<Void,Integer,Void>,你甚至可以更简化上面的代码。它很好用,谢谢。 - Peter Arandorenko
1
非常感谢。我有一个场景,需要同时执行两个异步任务来从Web服务器读取数据,但我发现服务器只接收一个URL,只要该特定请求未完成,第二个AsyncTask的URL就不会命中服务器。您的答案解决了我的问题 :) - Santhosh Gutta
1
我一直在得到这个的赞,谢谢。:) 但是我有印象很多人试图使用AsyncTask进行网络操作,而优秀的库,如VolleyGlideSwagger是更好的选择。在使用AsyncTask之前,请确保先检查这些库 :) - sulai
显示剩余2条评论

21
使@sulai的建议更通用:
@TargetApi(Build.VERSION_CODES.HONEYCOMB) // API 11
public static <T> void executeAsyncTask(AsyncTask<T, ?, ?> asyncTask, T... params) {
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
        asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    else
        asyncTask.execute(params);
}   

更通用的解决方案: AsyncTaskCompat.executeParallel(new MyAsyncTask, myprams); - Vikash Kumar Verma

11

为了包含@Arhimed在@sulai非常好的总结中无可挑剔的回答中的最新更新(更新4):

void doTheTask(AsyncTask task) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // Android 4.4 (API 19) and above
        // Parallel AsyncTasks are possible, with the thread-pool size dependent on device
        // hardware
        task.execute(params);
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { // Android 3.0 to
        // Android 4.3
        // Parallel AsyncTasks are not possible unless using executeOnExecutor
        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    } else { // Below Android 3.0
        // Parallel AsyncTasks are possible, with fixed thread-pool size
        task.execute(params);
    }
}

哇,太棒了。这意味着对于API >= KITKAT,.executeOnExecutor()现在是多余的,是吗? - Taslim Oseni

4
Android开发者提供了一个有效加载位图的示例,使用了自定义的AsyncTask(从jellybean复制),因此您可以在低于API 11的版本中使用executeOnExecutor。详情请参见此链接。下载代码并转到util包。

3

这是可能的。

我的安卓设备版本是4.0.4,android.os.Build.VERSION.SDK_INT是15。

我有3个下拉框。

Spinner c_fruit=(Spinner) findViewById(R.id.fruits);
Spinner c_vegetable=(Spinner) findViewById(R.id.vegetables);
Spinner c_beverage=(Spinner) findViewById(R.id.beverages);

我还有一个Async-Task类。

这是我的spinner加载代码。

RequestSend reqs_fruit = new RequestSend(this);
reqs_fruit.where="Get_fruit_List";
reqs_fruit.title="Loading fruit";
reqs_fruit.execute();

RequestSend reqs_vegetable = new RequestSend(this);
reqs_vegetable.where="Get_vegetable_List";
reqs_vegetable.title="Loading vegetable";
reqs_vegetable.execute();

RequestSend reqs_beverage = new RequestSend(this);
reqs_beverage.where="Get_beverage_List";
reqs_beverage.title="Loading beverage";
reqs_beverage.execute();

这个很完美地运行了。我的旋转器一个接一个地加载了。我没有使用executeOnExecutor。

下面是我的Async-task类:

public class RequestSend  extends AsyncTask<String, String, String > {

    private ProgressDialog dialog = null;
    public Spinner spin;
    public String where;
    public String title;
    Context con;
    Activity activity;      
    String[] items;

    public RequestSend(Context activityContext) {
        con = activityContext;
        dialog = new ProgressDialog(activityContext);
        this.activity = activityContext;
    }

    @Override
    protected void onPostExecute(String result) {
        try {
            ArrayAdapter<String> adapter = new ArrayAdapter<String> (activity, android.R.layout.simple_spinner_item, items);       
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            spin.setAdapter(adapter);
        } catch (NullPointerException e) {
            Toast.makeText(activity, "Can not load list. Check your connection", Toast.LENGTH_LONG).show();
            e.printStackTrace();
        } catch (Exception e)  {
            Toast.makeText(activity, "Can not load list. Check your connection", Toast.LENGTH_LONG).show();
            e.printStackTrace();
        }
        super.onPostExecute(result);

        if (dialog != null)
            dialog.dismiss();   
    }

    protected void onPreExecute() {
        super.onPreExecute();
        dialog.setTitle(title);
        dialog.setMessage("Wait...");
        dialog.setCancelable(false); 
        dialog.show();
    }

    @Override
    protected String doInBackground(String... Strings) {
        try {
            Send_Request();
            } catch (NullPointerException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        return null;
    }

    public void Send_Request() throws JSONException {

        try {
            String DataSendingTo = "http://www.example.com/AppRequest/" + where;
            //HttpClient
            HttpClient httpClient = new DefaultHttpClient();
            //Post header
            HttpPost httpPost = new HttpPost(DataSendingTo);
            //Adding data
            List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);

            nameValuePairs.add(new BasicNameValuePair("authorized","001"));

            httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
            // execute HTTP post request
            HttpResponse response = httpClient.execute(httpPost);

            BufferedReader reader;
            try {
                reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
                StringBuilder builder = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null) {
                    builder.append(line) ;
                }

                JSONTokener tokener = new JSONTokener(builder.toString());
                JSONArray finalResult = new JSONArray(tokener);
                items = new String[finalResult.length()]; 
                // looping through All details and store in public String array
                for(int i = 0; i < finalResult.length(); i++) {
                    JSONObject c = finalResult.getJSONObject(i);
                    items[i]=c.getString("data_name");
                }

            } catch (ClientProtocolException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2
你怎么看出它们不是按顺序执行的? - behelit
@behelit,它在工作时有关系吗?请阅读接受的答案以获取更多详细信息https://dev59.com/Fm855IYBdhLWcg3w7pGg#4072832 - Sajitha Rathnayake
2
抱歉,我不明白。如果您无法确定您的异步操作是顺序执行还是真正的异步执行,则确实很重要。此外,链接的答案建议使用executeonexecutor方法,而您在回答中并没有实际使用它。如果我漏掉了什么,请原谅。 - behelit
1
“一个接一个地,我的旋转器加载完毕。”你自己说了,这是串行执行,而不是并行执行。我们的每个AsyncTask都被添加到队列中并按顺序处理。这并没有回答OP的问题。 - Joshua Pinter
这是一个非常老的答案。如果这对您没有用,请参考已接受的答案。 - Sajitha Rathnayake

3

如果你想并行执行任务,你需要在Android 3.0版本之后调用方法executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "your task name");但是在Android 1.6之前和3.0之后,该方法不存在,因为它会自行并行执行。因此,我建议你在项目中自定义自己的AsyncTask类,以避免在不同的Android版本中抛出异常。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接