Executando várias AsyncTasks ao mesmo tempo - não é possível?

259

Estou tentando executar duas AsyncTasks ao mesmo tempo. (A plataforma é o Android 1.5, HTC Hero.) No entanto, apenas o primeiro é executado. Aqui está um trecho simples para descrever meu problema:

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.");
    }
}

A saída que espero é:

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

E assim por diante. No entanto, o que recebo é:

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

O segundo AsyncTask nunca é executado. Se eu alterar a ordem das instruções execute (), apenas a tarefa foo produzirá saída.

Estou perdendo algo óbvio aqui e / ou fazendo algo estúpido? Não é possível executar duas AsyncTasks ao mesmo tempo?

Editar: percebi que o telefone em questão roda o Android 1.5, atualizei o problema descr. adequadamente. Não tenho esse problema com um HTC Hero executando o Android 2.1. Hummm ...

rodion
fonte
Seu código funciona para mim, então o problema precisa estar em outro lugar. Você inseriu um filtro na visualização do LogCat? ;-) #
31410 mreichelt
Hum, isso é estranho. Não tenho nenhuma filtragem no logcat. Você está usando o 1.6 também? Se sim, qual telefone?
Rodion1
Opa, só percebi ele está correndo (antiga) Android 1.5
Rodion
1
Eu usei o Android 1.6 como alvo e um emulador do Android 2.1. Portanto, se o problema realmente ocorrer em um HTC Hero apenas com o Android 1.5 - vá se ferrar, tudo bem. ;-) O HTC Hero já possui a atualização para uma versão mais recente do Android. Eu não me incomodaria se houvesse alguns fabricantes que estragassem tudo. Além disso, eu não me importaria mais com o Android 1.5.
Mreichelt #
O AsyncTask deve ser usado para tarefas de duração mais curta de 5 ms. Vá para ThreadPoolExecutor ( developer.android.com/reference/java/util/concurrent/… ). Post relacionado: stackoverflow.com/questions/6964011/…
Ravindra babu

Respostas:

438

O AsyncTask usa um padrão de conjunto de encadeamentos para executar as coisas de doInBackground (). O problema é inicialmente (nas primeiras versões do sistema operacional Android) o tamanho do pool era apenas 1, o que significa que não há cálculos paralelos para várias tarefas AsyncTasks. Porém, mais tarde, eles corrigiram isso e agora o tamanho é 5, portanto, no máximo, 5 AsyncTasks podem ser executadas simultaneamente. Infelizmente, não me lembro em que versão exatamente eles mudaram isso.

ATUALIZAR:

Aqui está o que a API atual (2012-01-27) diz sobre isso:

Quando introduzidas pela primeira vez, as AsyncTasks foram executadas em série em um único encadeamento em segundo plano. Começando com DONUT, isso foi alterado para um pool de threads, permitindo que várias tarefas operassem em paralelo. Após o HONEYCOMB, está planejado alterar isso de volta para um único encadeamento para evitar erros comuns de aplicativo causados ​​pela execução paralela. Se você realmente deseja execução paralela, pode usar a versão executeOnExecutor (Executor, Params ...) deste método com THREAD_POOL_EXECUTOR; no entanto, consulte os comentários para avisos sobre seu uso.

DONUT é o Android 1.6, HONEYCOMB é o Android 3.0.

UPDATE: 2

Veja o comentário kabukode Mar 7 2012 at 1:27.

Acontece que, para APIs em que "um conjunto de threads que permite que várias tarefas operem em paralelo" é usado (a partir de 1.6 e finalizando em 3.0), o número de AsyncTasks em execução simultânea depende de quantas tarefas já foram passadas para execução, mas ainda não terminaram o seu doInBackground().

Isso é testado / confirmado por mim em 2.2. Suponha que você tenha um AsyncTask personalizado que durma apenas um segundo doInBackground(). AsyncTasks usam internamente uma fila de tamanho fixo para armazenar tarefas atrasadas. O tamanho da fila é 10 por padrão. Se você iniciar 15 suas tarefas personalizadas em uma linha, as 5 primeiras entrarão com as deles doInBackground(), mas o restante aguardará na fila por um encadeamento de trabalho livre. Assim que qualquer um dos 5 primeiros for concluído e, assim, liberar um thread de trabalho, uma tarefa da fila iniciará a execução. Portanto, neste caso, no máximo 5 tarefas serão executadas simultaneamente. No entanto, se você iniciar 16 suas tarefas personalizadas consecutivas, as 5 primeiras entrarão nas suas doInBackground(), as 10 restantes entrarão na fila, mas no dia 16 será criado um novo encadeamento de trabalho para iniciar a execução imediatamente. Portanto, neste caso, no máximo 6 tarefas serão executadas simultaneamente.

Há um limite de quantas tarefas podem ser executadas simultaneamente. Como AsyncTaskusa um executor de conjunto de encadeamentos com número máximo limitado de encadeamentos de trabalho (128) e a fila de tarefas atrasadas tem tamanho 10 fixo, se você tentar executar mais de 138 suas tarefas personalizadas, o aplicativo falhará java.util.concurrent.RejectedExecutionException.

A partir do 3.0, a API permite usar o executor do conjunto de threads personalizado por meio do AsyncTask.executeOnExecutor(Executor exec, Params... params)método Isso permite, por exemplo, configurar o tamanho da fila de tarefas atrasadas se o padrão 10 não for o que você precisa.

Como o @Knossos menciona, existe uma opção para usar AsyncTaskCompat.executeParallel(task, params);na biblioteca de suporte v.4 para executar tarefas em paralelo sem se preocupar com o nível da API. Este método foi descontinuado no nível da API 26.0.0.

UPDATE: 3

Aqui está um aplicativo de teste simples para jogar com o número de tarefas, execução serial versus paralela: https://github.com/vitkhudenko/test_asynctask

ATUALIZAÇÃO: 4 (obrigado @penkzhou por apontar isso)

A partir do Android 4.4 AsyncTaskse comporta de maneira diferente da descrita na seção UPDATE: 2 . Há uma correção para impedir a AsyncTaskcriação de muitos threads.

Antes do Android 4.4 (API 19), AsyncTaskhavia os seguintes campos:

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

No Android 4.4 (API 19), os campos acima são alterados para isso:

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

Essa alteração aumenta o tamanho da fila para 128 itens e reduz o número máximo de threads ao número de núcleos da CPU * 2 + 1. Os aplicativos ainda podem enviar o mesmo número de tarefas.

Vit Khudenko
fonte
11
Apenas para sua informação, o tamanho do pool principal é 5, mas o máximo é 128. Isso significa que, se você preencher sua fila, mais de 5 (até 128) poderão ser executados simultaneamente.
kabuko
2
@kabuko: você está absolutamente certo. Apenas investiguei isso na versão 2.2 e confirmando que, se 5 tarefas já estiverem em execução (o tamanho do pool principal é 5), ele começará a colocar o restante das tarefas na fila de tarefas atrasadas; no entanto, se a fila já estiver cheia (o tamanho máximo da fila será 10) ), inicia um thread de trabalho adicional para a tarefa, para que a tarefa inicie imediatamente.
Vit Khudenko
1
acabou de salvar meu dia, Caption herói :) Obrigado
Karoly
2
Atualize esta resposta para as novas funções compat . Exemplo:AsyncTaskCompat.executeParallel(task, params);
Knossos
1
@Arhimed Por favor, atualize a resposta, pois AsyncTaskCompat.executeParallel(task, params);agora está obsoleta #
Kirill Starostin
218

Isso permite a execução paralela em todas as versões do Android com 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);
}

Este é um resumo da excelente resposta da Arhimed.

Certifique-se de usar a API nível 11 ou superior como destino de criação do seu projeto. No Eclipse, é isso Project > Properties > Android > Project Build Target. Isso não prejudica a compatibilidade com versões anteriores dos níveis mais baixos da API. Não se preocupe, você receberá erros do Lint se usar acidentalmente os recursos introduzidos posteriormente minSdkVersion. Se você realmente quiser usar os recursos introduzidos mais tarde do que minSdkVersion, você pode suprimir esses erros usando anotações, mas, nesse caso, você precisa tomar cuidado sobre a compatibilidade do mesmo . Foi exatamente o que aconteceu no snippet de código acima.

sulai
fonte
3
Teve que alterar o TimerTask.THREAD_POOL_EXECUTOR para AsynTask.THREAD_POOL_EXECUTOR, em seguida, este exemplo funciona #
Ronnie
2
Para questões vazio que você pode fazer a chamada genérica: void startMyTask(AsyncTask<Void, ?, ?> asyncTask) (se o seu doInBackground leva void)
Peter
Você pode simplificar ainda mais o código acima se sua classe estender o AsyncTask <Vazio, Inteiro, Vazio>. Funcionou muito bem, obrigado.
Peter Arandorenko
1
Muito obrigado. Eu tive um cenário em que preciso executar duas tarefas assíncronas simultaneamente para ler dados do servidor da Web, mas achei que o servidor recebe apenas uma URL e, desde que essa solicitação específica não seja concluída, o segundo URL do AsyncTask não atingirá o servidor . Sua resposta resolvido meu problema :)
Santhosh Gutta
1
Continuo recebendo votos por isso, obrigado. :) Mas tenho a impressão de que muitas pessoas tentam usar o AsyncTask para redes, onde excelentes bibliotecas como Volley , Glide ou Swagger são melhores escolhas. Certifique-se de verificar isso antes de usar AsyncTask :)
Sulai
21

Tornando a sugestão @sulai mais genérica:

@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);
}   
AsafK
fonte
solução mais geral: AsyncTaskCompat.executeParallel (new MyAsyncTask, myprams);
21417 Viktor Kumar Verma
11

Apenas para incluir a atualização mais recente (ATUALIZAÇÃO 4) na resposta imaculada da @Arhimed no muito bom resumo do @sulai:

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);
    }
}
Ali Nem
fonte
Oh uau, incrível. Isso significa que .executeOnExecutor()agora é redundante para API> = KITKAT, sim?
Taslim Oseni 05/04
3

Isso é possível. Minha versão do dispositivo Android é 4.0.4 e android.os.Build.VERSION.SDK_INT é 15

Eu tenho 3 spinners

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

E também tenho uma classe Async-Tack.

Aqui está o meu código de carregamento do girador

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

Isso está funcionando perfeitamente. Um por um, meus giradores carregaram. Eu não fiz o usuário executeOnExecutor.

Aqui está minha classe de tarefas assíncronas

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();
        }
    }
}
Sajitha Rathnayake
fonte
2
como você vê que eles não são executados sequencialmente?
behelit
@behelit Isso importa quando está funcionando? Leia Resposta aceita para obter mais detalhes stackoverflow.com/a/4072832/2345900
Sajitha Rathnayake
2
Desculpe, eu não entendo. Se você não pode dizer se seus assíncronos são sequenciais ou verdadeiramente assíncronos, isso importa. Além disso, a resposta vinculada diz para usar o método executeonexecutor, que você realmente não está usando na sua resposta. Desculpe se eu perdi alguma coisa.
behelit
1
"Um por um, meus giradores carregaram." Você mesmo disse que é uma execução serial, não paralela. Cada uma de nossas AsyncTasks é adicionada a uma fila e processada sequencialmente. Isso não responde à pergunta do OP.
Joshua Pinter
Esta é uma resposta muito antiga. Se isto não é trabalho para você consulte resposta aceita
Sajitha Rathnayake
3

se você quiser executar tarefas paralelas, precisará chamar o método executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "your task name")após a versão Android 3.0; mas esse método não existe antes do Android 3.0 e depois da 1.6 porque é executado paralelamente por si só. Portanto, sugiro que você personalize sua própria classe AsyncTask em seu projeto, para evitar exceções em diferentes versões do Android.

alfa
fonte