Let's say I have an interface like so for components in my application to run jobs -
IJob {
IResult execute();
void cancel();
}
I want to set up my application so I run these jobs asynchronously. The expectation is that calling cancel should make execute return immediately with a result indicating it was cancelled.
What's the best way to set this up? I could just create a Thread object to run it that has additional methods to cancel, but I was also looking at the Future interface which I'm new to.
The problem with FutureTask is the cancel isn't graceful and won't allow me to call job.cancel(). Would it be a good idea to extend FutureTask and implement my own handling of it?
Calling IJob.execute() or FutureTask.run() will block the current thread, you need to schudle IJob or FutureTask.
Scheduling FutureTask is the best option, a consumer can call FutureTask.get() and wait for the result (even if you call IJob.cancel()).
I made a little demostration mocking IJob and IResult, it uses plain threads for scheduling, in production you should have an ExecutorService like previous post example.
As you can see, the main thread can check the status calling FutureTask.isDone(), basiclly you are checking if the result is already set. Setted result means that IJob's thread is finished.
You can call IJob.cancel() almost anytime for finishing the wrapped IJob in FutureTask, if the method behaves as like in your comments.
Mock Job:
public class MockJob implements IJob {
private boolean cancelled;
public MockJob() {
}
@Override
public IResult execute() {
int count = 0;
while (!cancelled) {
try {
count++;
System.out.println("Mock Job Thread: count = " + count);
if (count >= 10) {
break;
}
Thread.sleep(1000);
} catch (InterruptedException e) {
cancelled = true;
}
}
return new MockResult(cancelled, count);
}
@Override
public void cancel() {
cancelled = true;
}
}
Mock Result:
public class MockResult implements IResult {
private boolean cancelled;
private int result;
public MockResult(boolean cancelled, int result) {
this.cancelled = cancelled;
this.result = result;
}
public boolean isCancelled() {
return cancelled;
}
public int getResult() {
return result;
}
}
Main Class:
public class Main {
public static void main(String[] args) throws InterruptedException {
// Job
IJob mockJob = new MockJob();
// Async task
FutureTask<IResult> asyncTask = new FutureTask<>(mockJob::execute);
Thread mockJobThread = new Thread(asyncTask);
// Show result
Thread showResultThread = new Thread(() -> {
try {
IResult result = asyncTask.get();
MockResult mockResult = (MockResult) result;
Thread thread = Thread.currentThread();
System.out.println(String.format("%s: isCancelled = %s, result = %d",
thread.getName(),
mockResult.isCancelled(),
mockResult.getResult()
));
} catch (InterruptedException | ExecutionException ex) {
// NO-OP
}
});
// Check status
Thread monitorThread = new Thread(() -> {
try {
while (!asyncTask.isDone()) {
Thread thread = Thread.currentThread();
System.out.println(String.format("%s: asyncTask.isDone = %s",
thread.getName(),
asyncTask.isDone()
));
Thread.sleep(1000);
}
} catch (InterruptedException ex) {
// NO-OP
}
Thread thread = Thread.currentThread();
System.out.println(String.format("%s: asyncTask.isDone = %s",
thread.getName(),
asyncTask.isDone()
));
});
// Async cancel
Thread cancelThread = new Thread(() -> {
try {
// Play with this Thread.sleep, set to 15000
Thread.sleep(5000);
if (!asyncTask.isDone()) {
Thread thread = Thread.currentThread();
System.out.println(String.format("%s: job.cancel()",
thread.getName()
));
mockJob.cancel();
}
} catch (InterruptedException ex) {
// NO-OP
}
});
monitorThread.start();
showResultThread.start();
cancelThread.setDaemon(true);
cancelThread.start();
mockJobThread.start();
}
}
Output (Thread.sleep(5000)):
Thread-2: asyncTask.isDone = false
Thread-0: count = 1
Thread-2: asyncTask.isDone = false
Thread-0: count = 2
Thread-2: asyncTask.isDone = false
Thread-0: count = 3
Thread-2: asyncTask.isDone = false
Thread-0: count = 4
Thread-2: asyncTask.isDone = false
Thread-0: count = 5
Thread-3: job.cancel()
Thread-2: asyncTask.isDone = false
Thread-1: isCancelled = true, result = 5
Thread-2: asyncTask.isDone = true
Output (Thread.sleep(15000)):
Thread-2: asyncTask.isDone = false
Thread-0: count = 1
Thread-2: asyncTask.isDone = false
Thread-0: count = 2
Thread-2: asyncTask.isDone = false
Thread-0: count = 3
Thread-2: asyncTask.isDone = false
Thread-0: count = 4
Thread-2: asyncTask.isDone = false
Thread-0: count = 5
Thread-2: asyncTask.isDone = false
Thread-0: count = 6
Thread-2: asyncTask.isDone = false
Thread-0: count = 7
Thread-2: asyncTask.isDone = false
Thread-0: count = 8
Thread-2: asyncTask.isDone = false
Thread-0: count = 9
Thread-2: asyncTask.isDone = false
Thread-0: count = 10
Thread-1: isCancelled = false, result = 10
Thread-2: asyncTask.isDone = true