Dúvidas e problemas com múltiplas AsyncTask

Pessoal, boa tarde.

Eu tenho um processo de importação que interage com um web service recebendo os dados e persistindo em um banco de dados SQLite.

Antes eu havia feito uma AsyncTask ativada em um botão onde ela processava todas as importações dentro do doInBackground.

Mas agora por alguns problemas que tive, de verificação de conexão no meio da AsyncTask e também para dar um feedback para o usuário, já que é um processo demorado de certa forma (chegando a 40 mil registros).
Estou alterando o meu layout para exibir a listagem de tabelas, com um flag que mostrará para o usuário se aquela tabela foi importada com sucesso de acordo com a execução de seu respectivo AsyncTask.

Mas estou tentando algumas dúvidas, por exemplo daria para controlar esse modo que eu quero apenas com uma AsyncTask ou realmente eu teria que criar várias? E também eu fiz uns testes aqui com 3 e ele sempre começa a última para a primeira, este tipo de execução é aleatória de acordo com o SO ou ele passa por todas e executa a última que eu chamei o execute()?

E também qual o limite, pois pesquisei e andei lendo e pelo que vi são 5 o limite de simultâneas, as demais que forem chamadas após ficarão aguardando ou o SO não dará conta de gerenciar mais do que 5?

Para que entendam melhor a estrutura da importação, segue um exemplo dos métodos que estou utilizando para efetuar a importação:

Eu executo uma AsyncTask:

importarTabelaPreco tabelaPreco = new importarTabelaPreco(); tabelaPreco.execute();

No doInBackground eu chamo o método de importação da minha classe de TabelaPreco()

[code]@Override
protected Void doInBackground(Void… params) {
// TODO Auto-generated method stub

TabelaPreco tabelaPreco = new TabelaPreco();

tabelaPreco.importarDados();

return null;

}[/code]

Minha classe estende a classe de Persistencia e então eu faço a chamada do método de importação da Persistencia:

[code]@Override
public void importarDados(){

this.setWsNomeMetodo("GetTabelaPrecos");
this.setTabela(this.getClass().getSimpleName());
this.setCamposPK(new String[]{"Codigo"});
super.importarDados();

}[/code]

Que por sua vez o importarDados() da Persistencia, faz a requisição ao web service, recebe os dados carrega o objeto e grava no meu banco de dados.

O detalhe é que possuo 8 tabelas para serem carregadas via web service e algumas tem um grande volume de registros e entre uma requisição e outra eu quero verificar o status da internet e ainda retornar o feedback em tela para o usuário a cada uma que foi concluída…

Se alguém puder me ajudar ou tiver exemplos e documentações a respeito.

Obrigado desde já.

Ao meu ver depende da sua aplicação, caso queira e possa ter processamentos simultâneos dispare todas que precisar, quando o limite de thread for alcançado as demais vão ficar no aguardo para sua execução (já respondendo sua pergunta como o SO gerenciará as AT).

Caso queria apenas um processo continuo em background dando liberdade de disparar outras para outros fins na aplicação você pode chamar sua próxima AT quando uma terminar:

onPostExecute() { //do Something new MyAsyncTask2 ().execute (); }

Em relação a ordem de disparo não sei, na teoria era para ser iniciado na ordem que foi chamado o .execute();

[quote=fabriciov]Ao meu ver depende da sua aplicação, caso queira e possa ter processamentos simultâneos dispare todas que precisar, quando o limite de thread for alcançado as demais vão ficar no aguardo para sua execução (já respondendo sua pergunta como o SO gerenciará as AT).

Caso queria apenas um processo continuo em background dando liberdade de disparar outras para outros fins na aplicação você pode chamar sua próxima AT quando uma terminar:

onPostExecute() { //do Something new MyAsyncTask2 ().execute (); }

Em relação a ordem de disparo não sei, na teoria era para ser iniciado na ordem que foi chamado o .execute();[/quote]

Entendi, várias AT disparadas simultaneamente mesmo com o gerenciamento do SO, pode causar algum tipo de problema de performance? Ou o SO neste caso cuidará para que o limite estabelecido de Threads não atrapalhe o andamento de outros processos que possam estar ocorrendo ao mesmo tempo?

Em relação a isso não tenho idéia, mas gostei da pergunta fiquei bem curioso em relação.
Dei uma pesquisada e encontrei esse post no stackoverflow, pode ser que ajude:

Comentando que a AT no aplicativo teve maior prioridade sobre os recursos de hardware que a thread de UI. Mas na AT foi feito um hard loop… pessoalmente prefiro disparar um serviço do que deixar uma AT ativa durante todo o runtime.

Um artigo falando sobre os custos de uma AT.
http://steveliles.github.com/android_s_asynctask.html

apertei quote em vez de Edit ><

Olá
Qual o tempo gasto medido no relógio para executar essa AsyncTask (antes da alteração)?
Dependendo do tempo ela é candidata a rodar em um Service.
Agora, se pode usar várias threads ou uma, depende se cada tarefa é independente da outra.
Outro fator, é o tipo de equipamento que vai rodar o app, se tiver só um núcleo muitas threads pode não ajudar.

[quote=fabriciov]Em relação a isso não tenho idéia, mas gostei da pergunta fiquei bem curioso em relação.
Dei uma pesquisada e encontrei esse post no stackoverflow, pode ser que ajude:

Comentando que a AT no aplicativo teve maior prioridade sobre os recursos de hardware que a thread de UI. Mas na AT foi feito um hard loop… pessoalmente prefiro disparar um serviço do que deixar uma AT ativa durante todo o runtime.

Um artigo falando sobre os custos de uma AT.
http://steveliles.github.com/android_s_asynctask.html[/quote]

Obrigado, eu dei uma lida.
Estou decidindo ainda o que posso fazer no caso.

[quote=A H Gusukuma]Olá
Qual o tempo gasto medido no relógio para executar essa AsyncTask (antes da alteração)?
Dependendo do tempo ela é candidata a rodar em um Service.
Agora, se pode usar várias threads ou uma, depende se cada tarefa é independente da outra.
Outro fator, é o tipo de equipamento que vai rodar o app, se tiver só um núcleo muitas threads pode não ajudar.
[/quote]

Então o tempo gasto medido no relógio gira em torno de 5 a 8 minutos.
Depende da velocidade do plano de dados ou wi-fi.

Então cada tarefa é independente da outra.
Eu tenho algo em torno de 8 tarefas e eu vou chamar 1 método para cada uma em um web service, receber os dados e carregá-los na sua respectiva tabela.

E sim esta é uma grande preocupação minha, poder de processamento dos aparelhos. Eu estou testando em meu Motorola Razr, mas este aplicativo rodará em smartphones bem inferiores e minha preocupação é essa de acontecer algum tipo de travamento devido a quantidade de threads.
Por isso inclusive que inicialmente havia colocado apenas 1 AsyncTask para controlar todos os processos.

Bom Dia,

Enquanto a dar um feedback ao usuario, sobre o tempo da rotina , eu uso assim a AsyncTask

/*Neste exemplo eu estou autenticando um usuario novo, consulto um Web Service*/
	class LoginAsyncTask extends AsyncTask<String, Void, Boolean>{
		 private ProgressDialog progressDialog;  
		 private Exception erro; 
		 
		  protected void onPreExecute() {  
		    progressDialog = new ProgressDialog(LogarActivity.this);  
		    progressDialog.setMessage(getString(R.string.please_wait)); //mensagem do dialogo de progresso
                    
                    //se vc não quiser exibir a barra de progresso, então comente as 3 linhas abaixo    
                    progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
		    progressDialog.setProgress(0);
		    progressDialog.setMax(100);  
		    
                    progressDialog.show();  
		  }  
		
		@Override
		protected Boolean doInBackground(String... params) {
			
                       try{
                           
                          progressDialog.setProgress(10);    			
			/*
                             [....] Envio p/web service params[0] e params[1] que são respectivamente usuario e senha
                       */   
                       }catch(Exception e){
                                erro=e;
                               return false;
                       } finally{
                             progressDialog.setProgress(100);
                             //libera recursos tb, por exemplo fechar o sqlite após o uso
                       }
                      return true;
		}
		
		@Override
		protected void onPostExecute(Boolean result) {
			//fecha progresso
			progressDialog.dismiss();
			
			if(result){
				startActivity(intent);
				finish();
			}else{
				Toast.makeText(LogarActivity.this,"Sem acesso ao sistema, motivo: "+erro.getMessage() , Toast.LENGTH_LONG).show();
				erro.printStackTrace();
				etSenha.setText("");
				etUsuario.setText("");
				etUsuario.setFocusable(true);
			}
		}
	}

Faloww

Outro fator a considerar, se for rodar várias threads simultâneas, o consumo de memória vai aumentar muito. Pode estourar o limite.
Quanto tempo demora a tarefa da maior tabela (registrosXlength) ? Esse será o seu tempo limite inferior. A diferença compensa?
Para tarefas medidas em minutos no Android, é recomendável usar Service. E enviar Notifications para indicar progresso.
O uso de AsyncTask é recomendado para executar tarefas com duração de poucos segundos.
Se decidir usar varias threads, o importante é fazer testes em diversas configurações de aparelhos, considerando capacidade de processamento, memória, plano de dados, etc.
Abraço

[quote=A H Gusukuma]Outro fator a considerar, se for rodar várias threads simultâneas, o consumo de memória vai aumentar muito. Pode estourar o limite.
Quanto tempo demora a tarefa da maior tabela (registrosXlength) ? Esse será o seu tempo limite inferior. A diferença compensa?
Para tarefas medidas em minutos no Android, é recomendável usar Service. E enviar Notifications para indicar progresso.
O uso de AsyncTask é recomendado para executar tarefas com duração de poucos segundos.
Se decidir usar varias threads, o importante é fazer testes em diversas configurações de aparelhos, considerando capacidade de processamento, memória, plano de dados, etc.
Abraço[/quote]

Obrigado ao pessoal ai pela ajuda.

Vou avaliar a forma do feedback ao usuário que o BTO passou.

Mas eu vou pesquisar a implementação de Service porque essas requisições contabilizam até mais de 6 minutos e dependem da quantidade de dados.

E quanto a várias Threads realmente, estou preocupado com a performance.
Obrigado pela ajuda, vou pesquisar e ler a respeito de Service. Depois coloco a solução aqui e encerro o tópico. Se tiver alguma dúvida volto a postar aqui.

Alguma solução para este problema. Estou tendo esta mesma dificuldade.

Aqui neste meu código, estou tendo este mesmo problema, demora no processamento e parada inesperada da aplicação.

O problema só é detectado quando estou trabalhando com uma alta quantia de dados, caso o contrario funciona normalmente.

Em relação ao tempo de comunicação com o webservice e a conversão para o array esta OK ±.

O seguinte caso o qual ocorre o erro.

Estou carregando para um array de produtos (A quantia de 30.000 items), em meio ao processo de percorrer este array e gravar para a base local a aplicação para, não a um registro especifico, e nem cai para um except. To achando mesmo que é o tempo de fato que demora para processar estes registros.

Ha como definir um timeout ou prioridade no AsyncTask?

Em relação ao trabalhar com services, e interagir por exemplo com uma progress dialog.

Tem algum exemplo, a ser postado, sera que resolveria este problema?

package sys.commerce.view;

import com.google.gson.JsonArray;

import model.business.condicaoPagamento.CondicaoPagamento;
import model.business.empresa.Empresa;
import model.business.entidade.ViewEntidade;
import model.business.municipio.Municipio;
import model.business.pedidoVenda.PedidoVenda;
import model.business.produto.ViewProduto;
import model.business.regiao.Regiao;
import model.business.tipoPagamento.TipoPagamento;
import model.business.usuario.Sessao;
import model.business.usuario.Usuario;

import sys.commerce.R;
import sys.json.JsonUtil;
import sys.offline.dao.ClienteDB;
import sys.offline.dao.CondPagtoDB;
import sys.offline.dao.EmpresaDB;
import sys.offline.dao.FuncionarioDB;
import sys.offline.dao.MunicipioDB;
import sys.offline.dao.PedidoDB;
import sys.offline.dao.ProdutoDB;
import sys.offline.dao.RegiaoDB;
import sys.offline.dao.TipoPagtoDB;
import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.app.AlertDialog.Builder;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ProgressBar;
import android.widget.TextView;

public class FrmSync extends Activity {

	private ProgressBar pgSync = null;
	private TextView txtStatusSync = null;
	
	private CheckBox chkSyncDadosEmpresariais = null;
	private CheckBox chkSyncDadosRegionais = null;
	private CheckBox chkSyncDadosFinanceiros = null;
	private CheckBox chkSyncClientes = null;
	private CheckBox chkSyncTabPreco = null;
	private CheckBox chkSyncPedidos = null;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.frm_sync);

		pgSync = (ProgressBar) findViewById(R.id.pgSync);
		txtStatusSync = (TextView) findViewById(R.id.txtStatusSync);
		
		chkSyncDadosEmpresariais = (CheckBox) findViewById(R.id.chkSyncDadosEmpresariais);
		chkSyncDadosRegionais = (CheckBox) findViewById(R.id.chkSyncDadosRegionais);
		chkSyncDadosFinanceiros = (CheckBox) findViewById(R.id.chkSyncDadosFinanceiro);
		chkSyncClientes = (CheckBox) findViewById(R.id.chkSyncClientes);
		chkSyncTabPreco = (CheckBox) findViewById(R.id.chkSyncProdutos);
		chkSyncPedidos = (CheckBox) findViewById(R.id.chkSyncPedidos);
		
	}

	public void cancel(View view) {
		finish();
	}
	
	@Override
	public void onBackPressed() {
	}
	
	public void startSync(View view) {

		Builder msg = new Builder(FrmSync.this);
		msg.setMessage("Deseja efetuar o processo de sincronia?");
		msg.setPositiveButton("Sim", new DialogInterface.OnClickListener() {

			@Override
			public void onClick(DialogInterface arg0, int arg1) {
				final Processo processo = new Processo(FrmSync.this);
				processo.execute();
			}
		});
		msg.setNegativeButton("Nao", null);
		msg.show();

	}

	public class Processo extends AsyncTask<Integer, String, Integer> {

		private ProgressDialog progress;
		private Context context;

		public Processo(Context context) {
			this.context = context;
		}

		@Override
		protected void onPreExecute() {
			pgSync.setMax(20);
			pgSync.setProgress(0);

			progress = new ProgressDialog(context);
			progress.setMessage("Aguarde...");
			progress.setCancelable(false);
			progress.setIcon(R.drawable.sync_x128);
			progress.setCanceledOnTouchOutside(false);
			progress.setInverseBackgroundForced(false);
			progress.setTitle("Sincronização");
			progress.show();

			txtStatusSync.setText("Aguarde! Iniciando sincronia...");
		}

		@Override
		protected Integer doInBackground(Integer... paramss) {

			synchronized (paramss) {
				
				String[] resp = null;
				
				publishProgress("Conectando com o servidor WebSync", "t");
				
				if (JsonUtil.validarServer()) {
					
					if (chkSyncDadosEmpresariais.isChecked()) {
						publishProgress("Atualizando informações da empresa.", "t");
						try {
							resp = JsonUtil.get(String.format("empresa/buscarCNPJ?value=%s", Sessao.getCnpjEmpresa()));

							if (resp[0].equals("200")) {
								Empresa empresa = JsonUtil.getGson().fromJson(resp[1], Empresa.class);
								if (empresa != null) {
									new EmpresaDB(context).gravar(empresa);
								}
							}

						} catch (Exception e) {
							Log.e("Sync:Empresa >>", e.getMessage());
						} finally {
							resp = null;
						}
						
						publishProgress("Atualizando informações do usuário & dados de funcionário..", "t");
						try {
							resp = JsonUtil.get(String.format("consEntidades/buscarDOC?value=%s", Sessao.getCpfUsuario()));

							if (resp[0].equals("200")) {
								ViewEntidade funcionario = JsonUtil.getGson().fromJson(resp[1], ViewEntidade.class);
								if (funcionario != null) {
									new FuncionarioDB(context).gravar(funcionario);
								}
							}
							
							resp = JsonUtil.get(String.format("usuario/buscarDOC?value=%s", Sessao.getCpfUsuario()));

							if (resp[0].equals("200")) {
								Usuario usuario = JsonUtil.getGson().fromJson(resp[1], Usuario.class);
								if (usuario != null) {
									new FuncionarioDB(context).gravar(usuario);
								}
							}

						} catch (Exception e) {
							Log.e("Sync:Usuario >>", e.getMessage());
						} finally {
							resp = null;
						}
					}
					
					if (chkSyncDadosFinanceiros.isChecked()) {
						publishProgress("Sincronizando tabela: Tipo de Pagto.", "t");
						try {
							resp = JsonUtil.get("tipoPagamento/buscarTodos");

							if (resp[0].equals("200")) {

								final JsonArray array = JsonUtil.getJsonArray(resp[1]);

								final TipoPagtoDB tipoPagtoDB = new TipoPagtoDB(context);
								
								for (int i = 0; i < array.size(); i++) {
									
									TipoPagamento tipoPagto = JsonUtil.getGson().fromJson(array.get(i), TipoPagamento.class);
									if (tipoPagto != null) {
										tipoPagtoDB.gravar(tipoPagto);
									}
									
									publishProgress("Sincronizando tabela: Tipo de Pagamento\nRegistro(s): "+ (i + 1) + " de " + array.size(), "d");
								}

							}

						} catch (Exception e) {
							Log.e("Sync:TipoPagto >>", e.getMessage());
						} finally {
							resp = null;
						}

						publishProgress("Sincronizando tabela: Condição de Pagto.", "t");
						try {
							resp = JsonUtil.get("condicaoPagamento/buscarTodos");

							if (resp[0].equals("200")) {
								
								final JsonArray array = JsonUtil.getJsonArray(resp[1]);

								final CondPagtoDB condPagtoDB = new CondPagtoDB(context);
								
								for (int i = 0; i < array.size(); i++) {
									
									CondicaoPagamento condPagto = JsonUtil.getGson().fromJson(array.get(i), CondicaoPagamento.class);
									
									if (condPagto != null) {
										condPagtoDB.gravar(condPagto);
									}
									
									publishProgress("Sincronizando tabela: Condição de Pagamento\nRegistro(s): "+ (i + 1) + " de " + array.size(), "d");
								}

							}

						} catch (Exception e) {
							Log.e("Sync:CondPagto >>", e.getMessage());
						} finally {
							resp = null;
						}
					}
					
					if (chkSyncDadosRegionais.isChecked()) {
						publishProgress("Sincronizando tabela: Regiões", "t");
						try {
							resp = JsonUtil.get("regiao/buscarTodos");

							if (resp[0].equals("200")) {

								final JsonArray array = JsonUtil.getJsonArray(resp[1]);

								final RegiaoDB regiaoDB = new RegiaoDB(context);
								
								for (int i = 0; i < array.size(); i++) {
									
									Regiao regiao = JsonUtil.getGson().fromJson(array.get(i), Regiao.class);
									
									if (regiao != null) {
										regiaoDB.gravar(regiao);
									}
									
									publishProgress("Sincronizando tabela: Regiões\nRegistro(s): "+ (i + 1) + " de " + array.size(), "d");
								}

							}

						} catch (Exception e) {
							Log.e("Sync:Regiao >>", e.getMessage());
						} finally {
							resp = null;
						}

						publishProgress("Sincronizando tabela: Municípios", "t");
						try {
							resp = JsonUtil.get("municipio/buscarTodos");

							if (resp[0].equals("200")) {

								final JsonArray array = JsonUtil.getJsonArray(resp[1]);

								final MunicipioDB municipioDB = new MunicipioDB(context);
								
								for (int i = 0; i < array.size(); i++) {
									Municipio municipio = JsonUtil.getGson().fromJson(array.get(i), Municipio.class);
									
									if (municipio != null){
										municipioDB.gravar(municipio, false, false);
									}
									
									publishProgress("Sincronizando tabela: Municípios\nRegistro(s): "+ (i + 1) + " de " + array.size(), "d");
								}

							}

						} catch (Exception e) {
							Log.e("Sync:Municipio >>", e.getMessage());
						} finally {
							resp = null;
						}
					}
					

					if (chkSyncClientes.isChecked()) {
						
						publishProgress("Sincronizando tabela: Carteira de Clientes", "t");
						try {
							resp = JsonUtil.get("consEntidades/listarClientes");

							if (resp[0].equals("200")) {

								final JsonArray array = JsonUtil.getJsonArray(resp[1]);
																
								for (int i = 0; i < array.size(); i++) {
									
									ViewEntidade cliente = JsonUtil.getGson().fromJson(array.get(i), ViewEntidade.class);
									
									if (cliente != null){
										new ClienteDB(context).gravar(cliente);
									}
									
									publishProgress("Sincronizando tabela: Clientes\nRegistro(s): "+ (i + 1) + " de " + array.size(), "d");
								}

							}

						} catch (Exception e) {
							Log.e("Sync:Clientes >> ", e.getMessage());
						} finally {
							resp = null;
						}

					}

					if (chkSyncTabPreco.isChecked()) {
						publishProgress("Sincronizando tabela: Produtos", "t");
						try {
							resp = JsonUtil.get("consTabPreco/listar/1/1");

							if (resp[0].equals("200")) {

								final JsonArray array = JsonUtil.getJsonArray(resp[1]);
																
								for (int i = 0; i < array.size(); i++) {
									ViewProduto produto = JsonUtil.getGson().fromJson(array.get(i), ViewProduto.class);
									
									if (i == 3071) {
										array.size();
									}
									
									if (produto != null){
										new ProdutoDB(context).gravar(produto);
									}
									
									publishProgress("Sincronizando tabela: Produtos\nRegistro(s): "+ (i + 1) + " de " + array.size(), "d");
								}

							}

						} catch (Exception e) {
							Log.e("Sync:Produtos >> ", e.getMessage());
						} finally {
							resp = null;
						}
					}

					publishProgress("Aguarde! Concluindo procedimento...", "t");
					txtStatusSync.setText("Sincronização finalizada!");
					
					return 1;
				} else {
					return 0;
				}
				
				
			}
		}

		@Override
		protected void onPostExecute(Integer result) {
			pgSync.setProgress(pgSync.getMax());
			
			switch (result) {
			case 0:
				txtStatusSync.setText("Sem conexão com o Servidor de Dados : WebSync");
				break;

			default:
				txtStatusSync.setText("Sincronização finalizada!");
				break;
			}
			
			progress.dismiss();
		}

		@Override
		protected void onProgressUpdate(String... values) {
			if (values.length == 2) {
				if (values[1].equals("t")) {
					pgSync.setProgress(pgSync.getProgress() + 1);
					txtStatusSync.setText(values[0]);
				}
				progress.setMessage(values[0]);
			}			
		}
	}

}

[quote=aboult][quote=fabriciov]Ao meu ver depende da sua aplicação, caso queira e possa ter processamentos simultâneos dispare todas que precisar, quando o limite de thread for alcançado as demais vão ficar no aguardo para sua execução (já respondendo sua pergunta como o SO gerenciará as AT).

Caso queria apenas um processo continuo em background dando liberdade de disparar outras para outros fins na aplicação você pode chamar sua próxima AT quando uma terminar:

onPostExecute() { //do Something new MyAsyncTask2 ().execute (); }

Em relação a ordem de disparo não sei, na teoria era para ser iniciado na ordem que foi chamado o .execute();[/quote]

Entendi, várias AT disparadas simultaneamente mesmo com o gerenciamento do SO, pode causar algum tipo de problema de performance? Ou o SO neste caso cuidará para que o limite estabelecido de Threads não atrapalhe o andamento de outros processos que possam estar ocorrendo ao mesmo tempo?[/quote]

Nada garante a ordem de execução utilizando apenas execute().

Internamente a classe cria uma Thread-pool mas não trabalha muito bem com elas, e acaba ficando centenas de Threads mortas na aplicação que pode sim causar problemas de performance, a melhor solução é utilizar um Executor, mesmo por que, o proprio Android gerencia para que apenas duas streams estejam abertas da internet, então disparar 4 AT que se conectam na internet irão fazer duas delas ficarem idle até liberarem o lock da internet (isso é interno não se preocupe).

Para isso, crie seu Executor na classe e mantenha a instancia:

private final Executor networkExecutor = Executors.newCachedThreadPool(2);// ou Executors.newSingleThreadExecutor()

Então execute as tasks com:

task.executeOnExecutor(networkExecutor);

Que irá fazer o “schedulle” dela para ser executada assim que o executor estiver livre.

Sobre o feedback para o usuario, assim como explicaram acima: utilize o publishProgress que irá executar o onProgressUpdate na UIThread.

Outra coisa em relação à AsyncTasks: as tarefas podem ser executadas em paralelo ou sequencialmente, dependendo da versão do Android.
Isso tem causado problemas de processamento concorrente em muitos app´s com classes não thread-safe.

[quote=A H Gusukuma]Outra coisa em relação à AsyncTasks: as tarefas podem ser executadas em paralelo ou sequencialmente, dependendo da versão do Android.
Isso tem causado problemas de processamento concorrente em muitos app´s com classes não thread-safe.[/quote]

E também, utilizar um Executor.newSingleThreadExecutor() garante Thread-safety por confinamento (claro, só não invoque por outro lugar).