views:

1047

answers:

1

I'm just starting to look into Futures and the ScheduledExecutorService in Java, and I'm wondering why my Callable isn't running on the schedule I've indicated. In this sample code, the callable runs once, but the app never completes, nor does the task run again, which is what I expected to happen (I'm sure the problem is with my expectation).

Runnables work fine; Callables seem to block forever, but I'm not sure why.... What am I missing?

Thanks!

   public class ExecutorExample {

    /**
     * @param args
     * @throws ExecutionException 
     * @throws InterruptedException 
     */
    public static void main(String[] args) throws InterruptedException, ExecutionException {

     ScheduledExecutorService scheduler =  Executors.newScheduledThreadPool(5);

     FutureTask<ArrayList<String>> ft1 = new FutureTask<ArrayList<String>>(new Callable<ArrayList<String>>(){
      @Override
      public ArrayList<String> call() {
       ArrayList<String> stuff = new ArrayList<String>();
       for(int i = 0;i<10;i++){
        String thing ="Adding " + i + " to result"; 
        stuff.add(thing);
        System.out.println(thing);

       }
       return stuff;
      }});

     scheduler.scheduleAtFixedRate(ft1, 0, 1, TimeUnit.SECONDS);

     System.out.println(ft1.get());
     System.out.println(ft1.isDone());

    }
}
+2  A: 

The problem is that FutureTask is used, and as its class documentation says, "Once the computation has completed, the computation cannot be restarted or cancelled."

After the run method of FutureTask has been invoked once, subsequent invocations return immediately, without delegating to the task's Callable instance.

Only a Runnable can be used as a recurring task, and this doesn't allow passing back a result. Instead, give the Runnable task a callback that it can invoke at the end of it's run method, to report the results of each execution of the task back to listeners in other threads.

erickson
I'm not sure I follow this. FutureTask implements Runnable, and I'd expect it to call the call() method, and store the result. I'd then expect get() to either block until the job is done, or return the cached result immediately. So wouldn't the scheduler run the FutureTask, resulting in call() and the result being cached for later gathering ?
Brian Agnew
Yes, but only once. Any scheduled invocations after the first would call `run` on the FutureTask, but that run method "short-circuits" without invoking `call` on the Callable. But the scheduler is not aware of this, and keeps running to invoke the FutureTask, so the program doesn't terminate.
erickson
Makes perfect sense. I was so heads down in the javadocs for the Executors and its ilk and I completely missed that obvious line in FutureTask.As a follow-on question, if what I want to happen as the result of my task is that a BlockingQueue gets updated, is it preferable to just pass that queue into each runnable, or to implement a listener that would live outside the runnable which then manipulates the queue based on the results? Thanks again
Ah. Of course. I hadn't realised the symptoms were that it ran once! Voted up...
Brian Agnew
@marc - it depends on your application. If queue that needs to be updated lives in the same package with the scheduled task, I'd probably just pass it right to the task. If they're in different packages, I might wrap it up a little bit in some more restricted interface, so that the owner of the queue doesn't have to worry that the task is going to monkey with the queue in unacceptable ways.
erickson