简体   繁体   English

扩展FutureTask,如何处理取消

[英]Extending FutureTask, how to handle cancel

I've extended FutureTask from java.util.concurrent to provide callbacks to track the execution of tasks submitted to an ExecutorService . 我已经从java.util.concurrent扩展了FutureTask以提供回调,以跟踪提交给ExecutorService的任务的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);
    }

}

Now, what I see is if the task is submitted, but ends up queued and i cancel(true); 现在,我看到的是任务是否已提交,但最终排队并且我cancel(true); the task - the run() method still gets called - and the FutureTask.run() (likely) checks that the task is cancelled and doesn't call the wrapped callable. 任务FutureTask.run() run()方法仍然被调用FutureTask.run() (可能)检查任务已取消并且不调用包装的可调用对象。

Should I do eg 我应该做例如

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

Or should I still call super.run() ? 还是我仍然应该调用super.run() Both these approaches seems susceptible to race conditions in between checking for cancellation and doing something about it.. any thoughts appreciated. 在检查取消和对其进行任何操作之间,这两种方法似乎都容易受到比赛条件的影响。

You're right that there's a race there. 您说对了,那是对的。 FutureTask#done() will be called at most once , so if the task has already been canceled before it was ever run via RunnableFuture#run() , you'll have missed the call to FutureTask#done() . FutureTask#done()最多被调用一次 ,因此,如果在通过RunnableFuture#run()运行该任务之前已将其取消,则您将错过对FutureTask#done()的调用。

Have you considered a simpler approach that always issues a symmetric set of paired calls to ITaskStatusHandler#taskRunning() and ITaskStatusHandler#taskCompleted() , like so? 您是否考虑过一种更简单的方法,总是向ITaskStatusHandler#taskRunning()ITaskStatusHandler#taskCompleted()发出ITaskStatusHandler#taskRunning()称的配对调用?

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

Once RunnableFuture#run() is called, it's true that your task in running, or at least trying to be run. 调用RunnableFuture#run() ,您的任务确实正在运行,或者至少正在尝试运行。 Once FutureTask#run() is done, your task is no longer running. 一旦FutureTask#run()完成,您的任务将不再运行。 It so happens that in the case of cancellation, the transition is (nearly) immediate. 碰巧在取消的情况下,过渡是(几乎)立即进行的。

Trying to avoid calling ITaskStatusHandler#taskRunning() if the inner Callable or Runnable is never invoked by FutureTask#run() will require you to establish some shared structure between the Callable or Runnable and the FutureTask -derived type itself, so that when your inner function is first called you set some flag that the outer FutureTask -derived type can observe as a latch, indicating that yes, the function did start running before having been canceled. 如果内部的CallableRunnable从未被FutureTask#run()调用,则尝试避免调用ITaskStatusHandler#taskRunning()将需要您在CallableRunnableFutureTask派生的类型本身之间建立某种共享结构,以便在内部首先调用函数,您需要设置一些标志,外部的FutureTask派生类型可以将其视为闩锁,表明是的,该函数确实在取消之前开始运行。 However, by then, you had to have committed to calling ITaskStatusHandler#taskRunning() , so the distinction isn't so useful. 但是,到那时,您必须致力于调用ITaskStatusHandler#taskRunning() ,因此区别并不是那么有用。

I've been struggling with a similar design problem lately, and wound up settling on the symmetric before and after operations in my overridden FutureTask#run() method. 最近,我一直在努力解决类似的设计问题,并且在覆盖的FutureTask#run()方法中,在对称前后的 操作FutureTask#run()

Your problem is that your future tasks are still executed after you have called cancel on them, right? 您的问题是,您对它们调用了取消之后,将来的任务仍然可以执行,对吗?

After a task was submitted to the executor service it should be managed by the executor. 任务提交给执行者服务后,应由执行者对其进行管理。 (You can still cancel a single task if you like.) You should cancel the execution with the executor shutdownNow method . (如果愿意,您仍然可以取消单个任务。)您应该使用executor shutdownNow方法取消执行。 (This will call the cancel method of all submitted tasks.) An shutdown would still execute all submitted tasks. (这将调用所有已提交任务的cancel方法。)关机仍将执行所有已提交任务。

The executor does otherwise not "know" that a task was cancelled. 否则执行者不会“知道”任务已取消。 It will call the method independently of the internal state of the future task. 它将独立于将来任务的内部状态调用该方法。

The easiest approach would be to use the Executor framework as it is and write an Callable decorator. 最简单的方法是直接使用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);
     }
  }
}

The only problem is that task cannot be in the constructor of the decorator. 唯一的问题是任务不能在装饰器的构造器中。 (It's a parameter of the future task constructor.) To break this cycle you have to use a setter or some proxy work around with constructor injection. (这是将来任务构造函数的参数。)要打破此循环,您必须使用setter或一些代理来解决构造函数注入问题。 Maybe this is not needed for the callback at all and you can say: statusHandler.callableStarted(decorated) . 也许根本不需要回调,您可以说: statusHandler.callableStarted(decorated)

Depending on your requirements you may have to signal exceptions and interrupts. 根据您的要求,您可能必须发出异常和中断信号。

Basic implementation: 基本实现:

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();

Output: 输出:

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]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM