扩展FutureTask,如何处理取消

时间:2009-11-27 13:27:11

标签: java multithreading concurrency

我已从FutureTask扩展java.util.concurrent以提供回调以跟踪提交给ExecutorService的任务的执行情况。

public class StatusTask<V> extends FutureTask<V> {

    private final ITaskStatusHandler<V> statusHandler;

    public StatusTask(Callable<V> callable, ITaskStatusHandler<V> statusHandler){
        super(callable);
        if (statusHandler == null)
            throw new NullPointerException("statusHandler cannot be null");
        this.statusHandler = statusHandler;
        statusHandler.TaskCreated(this);
    }

    @Override
    public void run() {
        statusHandler.TaskRunning(this);
        super.run();
    }

    @Override
    protected void done() {
        super.done();
        statusHandler.TaskCompleted(this);
    }

}

现在,我看到的是,如果任务已提交,但最终排队并且cancel(true);任务 - run()方法仍然被调用 - 而FutureTask.run()(可能)检查任务是否被取消,并且不会调用包装的可调用对象。

我应该这样做吗

@Override
public void run() {
  if(!isCancelled()) {  
    statusHandler.TaskRunning(this);
    super.run();
  }
}

或者我还应该致电super.run()吗?这两种方法在检查取消和做某事之间似乎容易受到竞争条件的影响......任何想法都会受到赞赏。

2 个答案:

答案 0 :(得分:4)

你说那里有比赛是对的。 FutureTask#done()最多只会被称为 ,所以如果任务在通过RunnableFuture#run()运行之前已被取消,那么您将错过对{{1}的调用}}

您是否考虑过一种更简单的方法,始终向FutureTask#done()ITaskStatusHandler#taskRunning()发出一组对称的调用,如此?

ITaskStatusHandler#taskCompleted()

调用@Override public void run() { statusHandler.TaskRunning(this); try { super.run(); finally { statusHandler.TaskCompleted(this); } } 后,您的任务确实在运行,或者至少尝试运行。完成RunnableFuture#run()后,您的任务就不再运行了。碰巧的是,在取消的情况下,过渡是(几乎)立即的。

如果FutureTask#run()永远不会调用内部ITaskStatusHandler#taskRunning()Callable,则要求您在Runnable之间建立一些共享结构,以避免调用FutureTask#run()或者CallableRunnable - 派生类型本身,这样当你的内部函数首次被调用时,你设置一些标志,外部FutureTask - 派生类型可以观察为一个锁存器,表明是的,该功能确实在被取消之前开始运行。但是,到那时,你必须致力于调用FutureTask,所以这种区别并没有那么有用。

我最近一直在努力解决类似的设计问题,并在我的重写ITaskStatusHandler#taskRunning()方法中解决了之前的对称​​和之后的操作

答案 1 :(得分:2)

你的问题是你的未来任务在你取消之后仍然执行,对吗?

将任务提交给执行程序服务后,它应由执行程序管理。 (如果您愿意,您仍然可以取消单个任务。)您应该使用executor shutdownNow method取消执行。 (这将调用所有提交任务的cancel方法。)关闭仍将执行所有提交的任务。

执行人员不会“知道”任务被取消。它将独立于未来任务的内部状态调用该方法。

最简单的方法是按原样使用Executor框架并编写一个Callable装饰器。

class CallableDecorator{

  CallableDecorator(Decorated decorated){
     ...
  }

  setTask(FutureTask task){
    statusHandler.taskCreated(task);
  }

  void call(){
     try{
       statusHandler.taskRunning(task);
       decorated.call();
     }finally{
       statusHandler.taskCompleted(task);
     }
  }
}

唯一的问题是任务不能在装饰器的构造函数中。 (它是未来任务构造函数的参数。)要打破此循环,您必须使用setter或某些代理来解决构造函数注入问题。也许这根本不需要回调,您可以说:statusHandler.callableStarted(decorated)

根据您的要求,您可能需要发出异常和中断信号。

基本实施:

class CallableDecorator<T> implements Callable<T> {

    private final Callable<T> decorated;
    CallableDecorator(Callable<T> decorated){
        this.decorated = decorated;
    }

    @Override public T call() throws Exception {
        out.println("before " + currentThread());
        try {
            return decorated.call();
        }catch(InterruptedException e){
            out.println("interupted " + currentThread());
            throw e;
        }
        finally {
            out.println("after " + currentThread());
        }
    }
}

ExecutorService executor = newFixedThreadPool(1);
Future<Long> normal = executor.submit(new CallableDecorator<Long>(
        new Callable<Long>() {
            @Override
            public Long call() throws Exception {
                return System.currentTimeMillis();
            }
        }));
out.println(normal.get());

Future<Long> blocking = executor.submit(new CallableDecorator<Long>(
        new Callable<Long>() {
            @Override
            public Long call() throws Exception {
                sleep(MINUTES.toMillis(2)); // blocking call
                return null;
            }
        }));

sleep(SECONDS.toMillis(1));
blocking.cancel(true); // or executor.shutdownNow();

输出:

before Thread[pool-1-thread-1,5,main]
after Thread[pool-1-thread-1,5,main]
1259347519104
before Thread[pool-1-thread-1,5,main]
interupted Thread[pool-1-thread-1,5,main]
after Thread[pool-1-thread-1,5,main]