127

I have an AsyncTask class that I execute that downloads a big list of data from a website.

In the case that the end user has a very slow or spotty data connection at the time of use, I'd like to make the AsyncTask timeout after a period of time. My first approach to this is like so:

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

After starting the AsyncTask, a new handler is started that will cancel the AsyncTask after 30 seconds if it's still running.

Is this a good approach? Or is there something built into AsyncTask that is better suited for this purpose?

user1732313
  • 139
  • 1
  • 9
Jake Wilson
  • 88,616
  • 93
  • 252
  • 370

7 Answers7

44

Yes, there is AsyncTask.get()

myDownloader.get(30000, TimeUnit.MILLISECONDS);

Note that by calling this in main thread (AKA. UI thread) will block execution, You probably need call it in a separate thread.

yorkw
  • 40,926
  • 10
  • 117
  • 130
  • 9
    Seems like it defeats the purpose of using AsyncTask to begin with if this timeout method runs on the main UI thread... Why not just use the `handler` approach that I describe in my question? Seems much more straightforward because the UI thread doesn't freeze up while waiting to run the Handler's Runnable... – Jake Wilson Oct 25 '11 at 19:15
  • Okay thanks, I just wasn't sure if there was a proper way to do it or not. – Jake Wilson Oct 25 '11 at 21:57
  • 3
    I don't understand why you call get() in another thread. It looks weird because AsyncTask itself is a kind of thread. I think Jakobud's solution is more OK. – emeraldhieu Aug 08 '12 at 07:59
  • I think .get() is similar to posix thread's join, where the purpose is to wait for thread to complete. In your case it serves as a timeout, which is a circumstantial property. That's why you need to call .get() from handler or from another thread to avoid main UI blocking – Constantine Samoilenko Aug 23 '16 at 04:27
  • 2
    I know this is years later, but this still isn't built into android so I made a support class. I hope it helps someone out. https://gist.github.com/scottTomaszewski/3c9af91295e8871953739bb456de937b – Scott Tomaszewski Sep 19 '16 at 23:37
22

Use CountDownTimer Class in side the extended class for AsyncTask in the onPreExecute() method:

Main advantage, the Async monitoring done internally in the class.

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

change CountDownTimer(7000, 7000) -> CountDownTimer(7000, 1000) for example and it will call onTick() 6 times before calling onFinish(). This is good if you want to add some monitoring.

Thanks for all the good advice I got in this page :-)

Gilco
  • 1,326
  • 12
  • 13
20

In the case, your downloader is based upon an for an URL connection, you have a number of parameters that could help you to define a timeout without complex code:

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

  urlc.setConnectTimeout(15000);

  urlc.setReadTimeout(15000);

If you just bring this code into your async task, it is ok.

'Read Timeout' is to test a bad network all along the transfer.

'Connection Timeout' is only called at the beginning to test if the server is up or not.

  • 1
    I am in agreement. Using this approach is simpler, and it actually terminates the connection when the timeout is reached. Using the AsyncTask.get() approach you are only informed that the time limit has been reached, but the download is still being processed, and may actually complete at a later time -- causing more complications in the code. Thanks. – dazed Jan 05 '16 at 01:49
  • Please be aware that this is not enough when using lots of threads on very slow internet connections. More info: http://leakfromjavaheap.blogspot.nl/2013/12/are-connecttimeout-and-readtimeout.html and http://thushw.blogspot.nl/2010/10/java-urlconnection-provides-no-fail.html – Kapitein Witbaard Dec 02 '16 at 12:53
2

I don't think there's anything like that built into AsyncTask. Your approach seems to be a good one. Just be sure to periodically check the value of isCancelled() in your AsyncTask's doInBackground method to end this method once the UI thread cancels it.

If you want to avoid using the handler for some reason, you could check System.currentTimeMillis periodically within your AsyncTask and exit on timeout, although I like your solution better since it can actually interrupt the thread.

aleph_null
  • 5,766
  • 2
  • 24
  • 39
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();

         }
0

You can put one more condition to make cancellation more robust. e.g.,

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

Inspiring from question I have written a method which do some background task via AsyncTask and if processing takes more then LOADING_TIMEOUT then an alert dialogue to retry will appear.

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
  • 2,295
  • 24
  • 28