3

My Activity has an AsyncTask whose doInBackground method you can see below. I have to make multiple search request to multiple servers and in order to speed up the execution I've used Java's ExecutorService to make concurrent requests.

This works fine but I'd like my AsyncTask to stop whatever it is doing and quit, if I call the AsyncTask.cancel(); method with true as the mayInterruptIfRunning parameter. This is useful in situations where I need to stop the task when my Activity exits e.g. the "Back" button is pressed.

I've read that calling the cancel() method of the AsyncTask will prevent the onPostExecute method from being invoked, but the doInBackground will run until it is finished.

Is there a way, I could interrupt my Callables and force them to stop whatever it is they are doing and stop the AsyncTask.

I have posted an abridged version of my code here for brevity, but I have a bunch of a Callables, not just one.

Thanks.

  protected ArrayList<Result> doInBackground(final String... strQuery) {

      ArrayList<Result> objResults = new ArrayList<Result>();
      ExecutorService esrExecutor = Executors.newFixedThreadPool(2);
      Set<Callable<ArrayList<Result>>> setCallables = new HashSet<Callable<ArrayList<Result>>>();

      setCallables.add(new Callable<ArrayList<Result>>() {

          public ArrayList<Result> call() throws Exception {

              try {

                  MySearcher objSearcher = new MySearcher(Finder.this.objContext);
                  ArrayList<Result> objResults = new ArrayList<Result>();

                  objResults = objSearcher.doSearch(strQuery[0]);

                  return objResults;

              } catch (Indexer.LoginException e) {
                  return null;
              }

              return null;

          }

      });


      List<Future<ArrayList<Result>>> lstFutures;

      try {

          lstFutures = esrExecutor.invokeAll(setCallables);

          for(Future<ArrayList<Result>> futFuture : lstFutures){
              if (futFuture.get().isEmpty() == false)
                  objResults.addAll(futFuture.get());
          }

      } catch (InterruptedException e) {
          e.printStackTrace();
      } catch (ExecutionException e) {
          e.printStackTrace();
      }

      esrExecutor.shutdown();

      return objResults;

  }
Mridang Agarwalla
  • 43,201
  • 71
  • 221
  • 382
  • I think `futFuture.get()` is going to block the UI, right? – Lalit Poptani Sep 19 '12 at 07:10
  • @Lalit Poptani: Is that it? I haven't yet checked but that would seem logical that the `futFuture.get()` blocks. – Mridang Agarwalla Sep 19 '12 at 07:15
  • yeah I am just watching a video for Callable & Future on youtube and it says that I will block UI until the task gets completed. Here [it is](https://www.youtube.com/watch?v=lnbWFV4b7M4) – Lalit Poptani Sep 19 '12 at 07:18
  • I would insist to use AsyncTask and return value from there. You can check my answer [here](http://stackoverflow.com/questions/12453385/is-it-possible-to-get-results-of-asynctask-as-an-arraylist-hashmap/12453438#12453438) – Lalit Poptani Sep 19 '12 at 07:20

3 Answers3

3

I think that one solution would be to check for isCancelled() regularly in your doInBackground() method to check if the AsyncTask was cancelled and if you need to stop what you are doing earlier.

Alex
  • 25,147
  • 6
  • 59
  • 55
  • Where should this be placed? It seems that the `Callables` aren't executed until the `ExecutorService.invokeAll` method is called. Right after that comes the loop on which I'm fetching the results from the `Callables` and appending to my `List`. I'm confused because I can't see any code where I iterate and constantly wait for the `Callables` to complete. This is a bunch of `Callables` invoked from another thread, `AsyncTask`, invoked from yet another thread, `Acitivity`. I would need to send an interrupt even to the `Callables` so that the interrupt propagates through the whole chain, but how? – Mridang Agarwalla Sep 19 '12 at 07:02
  • 1
    You can check when iterating over the list of `Future`. If the AsyncTask has been cancelled, then you will call `cancel()` on each futures break the loop, shutdown the `ExecutorService` and return `null`/`Collections.emptyList()`. – Alex Sep 19 '12 at 07:19
  • Yes. That could work but wouldn't that still allow one iteration of the loop? It wouldn't be a "true" interrupt. What I'd like to accomplish is to shutdown the `ExecutorService` by calling `ExecutorService.shutdown()` the moment the `AsyncTask.cancel` method is called and kill all the threads (`Callables`) of the `ExecutorService` immediately. Is this even possible? – Mridang Agarwalla Sep 19 '12 at 08:08
  • Normally if you call `cancel(true)` on the asynTask then the `isCancelled()` method should directly respond `true`. So whether you will make an iteration of the loop or not depends on what time is elapsed between the `doInBackground` starts and the cancel method is called. I don't know a way to effectively shutdown the `ExecutorService` upon call to `cancel` on `AsyncTask`. Checking in the `doInBackground` is what I would do – Alex Sep 19 '12 at 08:43
1

Here is the place you iterate and wait:

   for(Future<ArrayList<Result>> futFuture : lstFutures){
          if (futFuture.get().isEmpty() == false)
              objResults.addAll(futFuture.get());
      }

the get() method for Future, Waits if necessary for the computation to complete, and then retrieves its result. you can check isCancelled() like this,

       for(Future<ArrayList<Result>> futFuture : lstFutures){
           if(isCancelled())
              break;
           else if (futFuture.get().isEmpty() == false)
              objResults.addAll(futFuture.get());
      }
      if(isCancelled())esrExecutor.shutdownNow();
jaredzhang
  • 1,158
  • 8
  • 12
  • @Alex, suggested the same thing below. Have a look my comment as why I feel that this wouldn't be the implementation for a "true" interrupt. http://stackoverflow.com/questions/12489636/how-can-i-interrupt-callables-in-an-asynctask-when-the-asynctask-cancel-method#comment16806465_12489792 – Mridang Agarwalla Sep 19 '12 at 08:10
0

Another option, if for example doSearch has a loop you could do it manually without the use of isCancelled or cancel. Your MySearcher class could have the following.

//in your MySearcher class
private AtomicBoolean cancelled = false;

public ArrayList<Result> doSearch(...){
    while(notFound && !cancelled.get()){
     // keep searching
    }
}

public void cancel(){
    cancelled.set(true);
}

now you can call cancel from anywhere.

vikki
  • 2,766
  • 1
  • 20
  • 26