Cancelor – a java task cancelation service
Lately I need to support task cancellation in a Java process I’m working on. The straightforward options I know to implement this are:
- Thread.interrupt() – the caller interrupts the worker thread (either directly or using Future.cancel()). Some say this is an erroneous approach, but I still haven’t figured out why. However, it is buggy on some recent versions on the JDK, and it is a bit fragile (what if the worker threads create worker threads that also need to be canceled?).
- Passing some object (AtomicBoolean?) down to every object you would like to support cancellation. These objects will check the value of this boolean, and should stop if it is false. They can pass the boolean to other objects / tasks. While this works, this boolean cannot be injected, and so must be manually passed along the call stack.
If you want the advantages of the second method, but don’t want to break IOC, here’s how:
First, the usage:
The listener object adds a dependency on ICancelor
public class Foo { public Foo(ICancelor cancelor) { this.cancelor = cancelor; ... } |
It then checks the cancellation state every now and then:
if (cancelor.wasTaskCanceled("TakeOverTheWorld")) return; |
The top-level thread that wishes to cancel a task simply calls
cancelor.cancelTask("TakeOverTheWorld"); |
And whenever a task is started, you should call
cancelor.resetTask("TakeOverTheWorld"); |
I’ll admit using strings for task names is a bit ugly, but this is not a terrible price to pay, assuming you have a few core tasks you intend to support. All that remains is the cancellation service itself:
/** * A cancellation service. */ public interface ICancelor { /** * Resets a task to "Not canceled" state */ void resetTask(String name); /** * Returns true iff the a cancelTask was called, and no resetTask was called afterwards. */ boolean wasTaskCanceled(String name); /** * Cancel a task */ void cancelTask(String name); } public class Cancelor implements ICancelor { private final ConcurrentHashMap tasks = new ConcurrentHashMap(); public void resetTask(String name) { tasks.put(name, true); } public boolean wasTaskCanceled(String name) { Boolean value = tasks.get(name); return value != null & value; } public void cancelTask(String name) { tasks.put(name, false); } } |
Because we rely on task names, there is an assumption here that all classes that play in the cancellation game belong to the same task semantically. If a class is a common class that doesn’t belong to a single task or flow, this approach does not work – in fact, I cannot think of an approach that will work in this case with dependency injection. The common class has to accept the cancellation signal somehow, it must either get an boolean explicit and not from the IOC container, or must check its interrupted state (or some other thread-local state) itself. Any smart ideas on how to solve this problem?