3

我目前正在使用 FutureTasks 和 Executors 在多线程环境中寻找一个讨厌的错误。基本思想是让固定数量的线程执行单独的 FutureTasks,计算结果将显示在一张表中(不要介意这里的 GUI 方面)。

看了这么久,开始怀疑自己的智商了。

考虑这段代码:

public class MyTask extends FutureTask<Result> {
   private String cellId;
   ...
   protected void done() {
      if (isCancelled()) return;
      try {
          Result r = get(); // should not wait, because we are done
          ... // some processing with r
          sendMessage(cellId, r);
      } catch (ExecutionException e) { // thrown from get
         ... 
      } catch (InterruptedException e) { // thrown from get
         ... 
      }
   }
   ...
}

done()由处理 MyTask 实例的 Executor 调用时,我检查我是否到达那里,因为任务已被取消。如果是这样,我会跳过所有剩余的活动,尤其是我不调用sendMessage().

FutureTask.done() 的文档说:

当此任务转换到状态 isDone 时调用受保护的方法(无论是正常还是通过取消)。默认实现什么也不做。子类可以覆盖此方法以调用完成回调或执行簿记。请注意,您可以在此方法的执行中查询状态以确定此任务是否已被取消。(API 参考

但是我没有从文档中得到的是执行FutureTask的语义。如果我一开始就通过了检查,但之后其他线程调用了我的方法怎么办?这会导致我的任务改变主意并从那时起回复吗? done()isCancelled()cancel()isCancelled() == true

如果是这样,我以后怎么知道消息是否已发送?看着isDone()只会告诉我任务的执行已经完成,但当时isCancelled()也是如此,我不知道它是否能及时发送消息。

也许这很明显,但我现在还没有真正看到。

4

4 回答 4

4

FutureTask#done()对于任何给定的实例,调用不超过一次,并且仅出于一个原因调用它 -run()完成有或没有错误,或者cancel()在前面的任何一个事件发生之前运行。任何这些结果的完成记录都是锁定的。完成的原因FutureTask不能改变,无论竞争事件看似“同时”发生。

因此,FutureTask#done()只有其中一个isCancelled()orisDone()将在那时和永远返回 true。很难区分是isDone()通过错误报告真实还是成功完成。您不能重写set()setException(Throwable)果断,因为两者都委托给内部AQS来决定是否应该坚持记录成功产生值或遇到异常的尝试。覆盖任一方法只会让您知道它已被调用,但您无法观察基本实现做出的决定。如果任一事件发生“太晚”(例如,取消之后),记录值或异常的尝试将被忽略。

研究实现,我看到从错误中辨别出未取消的成功结果的唯一方法是硬着头皮调用get().

于 2009-11-22T01:08:24.613 回答
3

为什么不根据ExecutorService返回的Future<V>对象的结果在任务“外部”发送消息?我使用过这种模式,它似乎运行良好:通过ExecutorService提交一堆Callable<V>任务。然后,对于每个主要任务,提交一个次要任务,等待主要任务的Future<V>并仅在Future<V>指示主要任务成功完成时执行一些后续操作(如发送消息). 这种方法没有猜测。当对Future<V>.get()的调用返回时,只要您不这样做,就可以保证任务已达到终止状态得到它需要一个超时参数。

如果您采用这种方法,您应该使用两个单独的ExecutorService实例:一个用于主要任务,一个用于辅助任务。这是为了防止死锁。当线程池大小受到限制时,您不希望辅助任务启动并可能阻止主要任务启动。

根本不需要扩展FutureTask<V>。只需将您的任务实现为Callable<V>对象。但是,如果出于某种原因您想检测是否从Callable<V>代码中取消了任务,只需使用Thread.interrupted()检查线程的中断状态。

于 2009-08-05T21:44:35.360 回答
3

从 API(强调我的):

公共布尔取消(布尔可能中断IfRunning)

从接口复制的描述:未来

尝试取消此任务的执行。如果任务已经完成、已被取消或由于其他原因无法取消,则此尝试将失败。

因此,FutureTask 假设您无法在任务转换到 isDone 阶段时取消它。

于 2009-08-05T18:51:31.760 回答
-1

我建议编写一个小测试用例,允许您在实例挂起时调用cancel()Future看看done()会发生什么。

于 2009-08-05T20:51:45.200 回答