views:

507

answers:

3

In the code below, I'm catching a TimeoutException after 100 seconds as intended. At this point I would expect the code to exit from main and the program to terminate but it keeps printing to the console. How do I get the task to stop executing after timeout?

private static final ExecutorService THREAD_POOL = Executors.newCachedThreadPool();

private static <T> T timedCall(Callable<T> c, long timeout, TimeUnit timeUnit) throws InterruptedException, ExecutionException, TimeoutException {
    FutureTask<T> task = new FutureTask<T>(c);
    THREAD_POOL.execute(task);
    return task.get(timeout, timeUnit);
}


public static void main(String[] args) {

 try {
     int returnCode = timedCall(new Callable<Integer>() {
         public Integer call() throws Exception {
          for (int i=0; i < 1000000; i++) {
           System.out.println(new java.util.Date());
           Thread.sleep(1000);
          }
       return 0;
         }
     }, 100, TimeUnit.SECONDS);
 } catch (Exception e) {
  e.printStackTrace();
  return;
 }


}
A: 

Once you caught the TimeoutException, you need to call the cancel(true) method of your task ...

or shut down your ExecutorService by calling shutdownNow() ...

or quit the VM by calling System.exit(0)

depending on your needs

Zed
+1  A: 

You need to cancel your task on timeout (and interrupt its thread). That's what cancel(true) method is for. :

private static final ExecutorService THREAD_POOL = Executors.newCachedThreadPool();

private static <T> T timedCall(FutureTask<T> task, long timeout, TimeUnit timeUnit) throws InterruptedException, ExecutionException, TimeoutException {
    THREAD_POOL.execute(task);
    return task.get(timeout, timeUnit);
}


public static void main(String[] args) {
        try {
            FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {
                public Integer call() throws Exception {
                        for (int i=0; i < 1000000; i++) {
                                if (Thread.interrupted()) return 1;
                                System.out.println(new java.util.Date());
                                Thread.sleep(1000);
                        }
                        return 0;
                }
            });
            int returnCode = timedCall(task, 100, TimeUnit.SECONDS);
        } catch (Exception e) {
                e.printStackTrace();
                task.Cancel(true);
        }
        return;
}
Alexey Romanov
!task.cancelled() should be !isCancelled() as djna originally wrote
Zed
I think it's much better to use interruptions. First, your callable code doesn't need to know about `task` at all. Second, when you're using various blocking operations in your callable code (like `Thread.sleep()`), these will not react to task.isCancelled(), but usually react to interruptions. So using `cancel(true)` and making your code aware of interruptions is usually the best way to do this. (Your code will be also more general, because interruptions mechanism is so widely used in Java)
Peter Štibraný
I guess my point is that "Interruption is a cooperative mechanism." (http://www.ibm.com/developerworks/java/library/j-jtp05236.html)
Peter Štibraný
@Zed fixed this.
Alexey Romanov
@Peter Nice article! I've changed the answer accordingly.
Alexey Romanov
+1  A: 

Your Callable must to be able to stop quickly, when needed.

Your code:

public Integer call() throws Exception {
    for (int i=0; i < 1000000 && !task.cancelled(); i++) {
        System.out.println(new java.util.Date());
        Thread.sleep(1000); // throws InterruptedException when thread is interrupted
    }
    return 0;
}

Is already able to do that thanks to calling Thread.sleep(). Point is that futureTask.cancel(true) will interrupt other thread, and your code needs to react to this interruption. Thread.sleep() does that. If you didn't use Thread.sleep() or other interruptible blocking code, you would have to check Thread.currentThread().isInterrupted() by yourself, and quit as soon as possible (e.g. by throwing new InterruptedException()) when you find this to be true.

You need to call futureTask.cancel(true); from your exception handler to cancel and interrupt thread which runs your task.

My advice is to learn about interruption mechanism (this is great article: Dealing with InterruptedException), and use it.

Peter Štibraný