views:

114

answers:

2

I am trying useing futures for the first time. It seems smart that you can cancel a job but it is not working as expected. In the example below only the first job is cancelled. The rest are completed. Have I missunderstood the use of futures?

public class ThreadExample 
{
    public static void main(String[] args) throws InterruptedException, ExecutionException 
    {
        int processors = Runtime.getRuntime().availableProcessors();
        System.out.println("Processors: " + processors);
        ExecutorService es = Executors.newFixedThreadPool(processors);
        int nowork = 10;
        Future<Integer>[] workres = new Future[nowork];
        for(int i = 0; i < nowork; i++)
        {
            workres[i] = es.submit(new SomeWork(i));
        }
        for(int i = 0; i < nowork; i++) 
        {
            if(i % 2 == 0)
            {
                System.out.println("Cancel");
                workres[i].cancel(true);
            }
            if(workres[i].isCancelled())
            {
                System.out.println(workres[i] + " is cancelled");
            }
            else
            {
                System.out.println(workres[i].get());
            }
        }
        es.shutdown();
    }
}

class SomeWork implements Callable<Integer> 
{
    private int v;
    public SomeWork(int v) 
    {
        this.v = v;
    }

    @Override
    public Integer call() throws Exception
    {
        TimeUnit.SECONDS.sleep(5);
        System.out.println(v + " done at " + (new Date()));
        return v;
    }
}

The output:

Processors: 4
Cancel
java.util.concurrent.FutureTask@10d448 is cancelled
4 done at Wed May 12 17:47:05 CEST 2010
2 done at Wed May 12 17:47:05 CEST 2010
1 done at Wed May 12 17:47:05 CEST 2010
3 done at Wed May 12 17:47:05 CEST 2010
1
Cancel
2  
3
Cancel
4
5 done at Wed May 12 17:47:10 CEST 2010
7 done at Wed May 12 17:47:10 CEST 2010
8 done at Wed May 12 17:47:10 CEST 2010
6 done at Wed May 12 17:47:10 CEST 2010  
5
Cancel
6
7
Cancel
8
9 done at Wed May 12 17:47:15 CEST 2010  
9
+2  A: 

The Future#cancel() will not terminate/interrupt the already running jobs. It will only cancel the not-yet-running jobs.

Update: polygenelubricants nailed the root cause down (+1): here is the improved code:

int processors = Runtime.getRuntime().availableProcessors();
System.out.println("Processors: " + processors);
ExecutorService es = Executors.newFixedThreadPool(processors);
int nowork = 10;
Future<Integer>[] workers = new Future[nowork];

for (int i = 0; i < nowork; i++) {
    final int ii = i;
    workers[i] = es.submit(new Callable<Integer>() {
        public Integer call() throws Exception {
            return ii;
        }
    });
}

for (int i = 0; i < nowork; i++) {
    if (i % 2 == 0) {
        System.out.println("Cancel worker " + i);
        workers[i].cancel(true);
    }
}

for (int i = 0; i < nowork; i++) {
    if (workers[i].isCancelled()) {
        System.out.println("Worker " + i + " is cancelled");
    } else {
        System.out.println("Worker " + i + " returned: " + workers[i].get());
    }
}

es.shutdown();

Result:

Processors: 2
Cancel worker 0
Cancel worker 2
Cancel worker 4
Cancel worker 6
Cancel worker 8
Worker 0 is cancelled
Worker 1 returned: 1
Worker 2 is cancelled
Worker 3 returned: 3
Worker 4 is cancelled
Worker 5 returned: 5
Worker 6 is cancelled
Worker 7 returned: 7
Worker 8 is cancelled
Worker 9 returned: 9

(note that it's workers, not workres).

BalusC
"If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task." Imo cancel is a very bad word/methodname to use if you can only use it on not-yet-running jobs :/
bobjink
I myself find the difference between `cancel` and `interrupt` (or `abort` or `terminate`) yet clear enough.
BalusC
You can call `.cancel(true)` to force a job that has started to interrupt.
Finbarr
+1 back to you! (OMG are we colluding?)
polygenelubricants
@poly: actually, almost all your answers are worth a vote :) I however seldom comment an upvote.
BalusC
+3  A: 

The problem is that your cancellation loop is overlapping with your get() loop, which blocks. I think you want to have 2 loops, don't you? One loop that cancels even numbered jobs, and then a second loop that checks which ones are cancelled and which ones aren't, and then get() accordingly.

The way it's written right now, before the loop even had a chance to cancel workres[2], it checked and asked for get() from workres[1].

So I think you need 3 phases:

1. The `submit()` loop
2. The selective `cancel()` loop
3. The selective `get()` loop (which blocks)
polygenelubricants
Thanks! Did not think about get() being a method that blocks a problem.
bobjink