Android - Definindo um tempo limite para uma AsyncTask?

125

Tenho uma AsyncTaskclasse que executo que baixa uma grande lista de dados de um site.

No caso de o usuário final ter uma conexão de dados muito lenta ou irregular no momento do uso, eu gostaria de fazer o AsyncTasktempo limite após um período de tempo. Minha primeira abordagem para isso é assim:

MyDownloader downloader = new MyDownloader();
downloader.execute();
Handler handler = new Handler();
handler.postDelayed(new Runnable()
{
  @Override
  public void run() {
      if ( downloader.getStatus() == AsyncTask.Status.RUNNING )
          downloader.cancel(true);
  }
}, 30000 );

Depois de iniciar o AsyncTask, um novo manipulador é iniciado que cancelará AsyncTaskapós 30 segundos, se ainda estiver em execução.

Será esta uma boa abordagem? Ou há algo embutido AsyncTaskque é mais adequado para esse fim?

Jake Wilson
fonte
18
Depois de tentar várias abordagens diferentes, concluí que sua pergunta deveria ser a resposta aceita.
26412 JohnEye
Obrigado pela pergunta, eu realmente caí no mesmo problema e seu código me ajudou +1
Ahmad Dwaik 'Warlock'
1
Sua pergunta é em si uma resposta boa +50 :)
karthik kolanji

Respostas:

44

Sim, existe AsyncTask.get ()

myDownloader.get(30000, TimeUnit.MILLISECONDS);

Observe que, chamando isso no thread principal (AKA. Thread da interface do usuário) irá bloquear a execução. Você provavelmente precisará chamá-lo em um thread separado.

yorkw
fonte
9
Parece que isso acaba com o propósito de usar o AsyncTask, se esse método de tempo limite for executado no thread principal da interface do usuário ... Por que não usar apenas a handlerabordagem descrita na minha pergunta? Parece muito mais simples porque o segmento interface do usuário não congelar-se enquanto espera para ser executado do Handler Runnable ...
Jake Wilson
Ok, obrigado, eu não tinha certeza se havia uma maneira adequada de fazer isso ou não.
Jake Wilson
3
Não entendo por que você chama get () em outro segmento. Parece estranho porque o AsyncTask em si é um tipo de thread. Acho que a solução de Jakobud está mais correta.
Emeraldhieu 8/08/2012
Eu acho que .get () é semelhante à junção do thread posix, onde o objetivo é aguardar a conclusão do thread. No seu caso, ele serve como um tempo limite, que é uma propriedade circunstancial. É por isso que você precisa chamar .get () do manipulador ou de outro segmento para evitar principal UI bloqueando
Constantine Samoilenko
2
Eu sei que isso é anos depois, mas ainda não está embutido no Android, então fiz uma aula de suporte. Espero que ajude alguém. gist.github.com/scottTomaszewski/…
Scott Tomaszewski
20

No caso, o seu downloader é baseado em uma conexão para URL, você tem vários parâmetros que podem ajudá-lo a definir um tempo limite sem código complexo:

  HttpURLConnection urlc = (HttpURLConnection) url.openConnection();

  urlc.setConnectTimeout(15000);

  urlc.setReadTimeout(15000);

Se você apenas colocar esse código em sua tarefa assíncrona, tudo bem.

O 'Tempo limite de leitura' é testar uma rede ruim durante toda a transferência.

O 'Tempo limite da conexão' é chamado apenas no início para testar se o servidor está ativo ou não.

Clement Soullard
fonte
1
Eu concordo. O uso dessa abordagem é mais simples e, na verdade, finaliza a conexão quando o tempo limite é atingido. Usando a abordagem AsyncTask.get (), você é informado apenas de que o prazo foi atingido, mas o download ainda está sendo processado e pode ser concluído posteriormente - causando mais complicações no código. Obrigado.
Atordoado
Esteja ciente de que isso não é suficiente ao usar muitos threads em conexões de Internet muito lentas. Mais informações: leakfromjavaheap.blogspot.nl/2013/12/… e thushw.blogspot.nl/2010/10/…
Kapitein Witbaard 02 /
20

Use a classe CountDownTimer ao lado da classe estendida para AsyncTask no método onPreExecute ():

Principal vantagem, o monitoramento assíncrono feito internamente na classe.

public class YouExtendedClass extends AsyncTask<String,Integer,String> {
...
public YouExtendedClass asyncObject;   // as CountDownTimer has similar method -> to prevent shadowing
...
@Override
protected void onPreExecute() {
    asyncObject = this;
    new CountDownTimer(7000, 7000) {
        public void onTick(long millisUntilFinished) {
            // You can monitor the progress here as well by changing the onTick() time
        }
        public void onFinish() {
            // stop async task if not in progress
            if (asyncObject.getStatus() == AsyncTask.Status.RUNNING) {
                asyncObject.cancel(false);
                // Add any specific task you wish to do as your extended class variable works here as well.
            }
        }
    }.start();
...

altere CountDownTimer (7000, 7000) -> CountDownTimer (7000, 1000), por exemplo, e ele chamará onTick () 6 vezes antes de chamar onFinish (). Isso é bom se você deseja adicionar algum monitoramento.

Obrigado por todos os bons conselhos que recebi nesta página :-)

Gilco
fonte
2

Eu não acho que exista algo assim incorporado ao AsyncTask. Sua abordagem parece ser boa. Apenas verifique periodicamente o valor de isCancelled () no método doInBackground do AsyncTask para finalizar esse método depois que o thread da interface do usuário o cancelar.

Se você quiser evitar o uso do manipulador por algum motivo, verifique System.currentTimeMillis periodicamente em seu AsyncTask e saia no tempo limite, embora eu goste mais da sua solução, pois ela pode realmente interromper o encadeamento.

aleph_null
fonte
0
         Context mContext;

         @Override
         protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
                                    mContext = this;

            //async task
            final RunTask tsk = new RunTask (); 
            tsk.execute();

            //setting timeout thread for async task
            Thread thread1 = new Thread(){
            public void run(){
                try {
                    tsk.get(30000, TimeUnit.MILLISECONDS);  //set time in milisecond(in this timeout is 30 seconds

                } catch (Exception e) {
                    tsk.cancel(true);                           
                    ((Activity) mContext).runOnUiThread(new Runnable()
                    {
                         @SuppressLint("ShowToast")
                        public void run()
                         {
                            Toast.makeText(mContext, "Time Out.", Toast.LENGTH_LONG).show();
                            finish(); //will close the current activity comment if you don't want to close current activity.                                
                         }
                    });
                }
            }
        };
        thread1.start();

         }
Mrityunjaya
fonte
2
Considere a adição de alguma explicação bem
Saif Asif
0

Você pode colocar mais uma condição para tornar o cancelamento mais robusto. por exemplo,

 if (downloader.getStatus() == AsyncTask.Status.RUNNING || downloader.getStatus() == AsyncTask.Status.PENDING)
     downloader.cancel(true);
Vinnig
fonte
0

Inspirando-me na pergunta, escrevi um método que executa alguma tarefa em segundo plano via AsyncTask e, se o processamento demorar mais que LOADING_TIMEOUT, será exibida uma caixa de diálogo de alerta para tentar novamente.

public void loadData()
    {
        final Load loadUserList=new Load();
        loadUserList.execute();
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (loadUserList.getStatus() == AsyncTask.Status.RUNNING) {
                    loadUserList.cancel(true);
                    pDialog.cancel();
                    new AlertDialog.Builder(UserList.this)
                            .setTitle("Error..!")
                            .setMessage("Sorry you dont have proper net connectivity..!\nCheck your internet settings or retry.")
                            .setCancelable(false)
                            .setPositiveButton("Retry", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialogInterface, int i) {
                                    loadData();
                                }
                            })
                            .setNegativeButton("Exit", new DialogInterface.OnClickListener() {
                                @Override


                      public void onClick(DialogInterface dialogInterface, int i) {
                                System.exit(0);
                            }
                        })
                        .show();
            }
        }
    }, LOADING_TIMEOUT);
    return;
}
Abhishek
fonte