Java 5:java.util.concurrent.FutureTask - cancel()和done()的语义

发布于 2024-07-30 04:15:53 字数 1648 浏览 8 评论 0原文

我目前正在使用 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
         ... 
      }
   }
   ...
}

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

FutureTask.done() 的文档说:

当此任务转换到状态 isDone 时(无论是正常还是通过取消),将调用受保护的方法。 默认实现不执行任何操作。 子类可以重写此方法来调用完成回调或执行簿记。 请注意,您可以在该方法的实现中查询状态来确定该任务是否已被取消。 (API 参考< /a>)

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

如果是这样,我以后如何知道消息是否已发送? 查看 isDone() 只会告诉我任务的执行已完成,但由于 isCancelled() 也为 true,所以我不知道它是否已完成及时将消息发送出去。

也许这是显而易见的,但我现在还没有真正看到它。

I am currently hunting a nasty bug in a multi-threaded environment using FutureTasks and Executors. The basic idea is this to have a fixed number of threads execute individual FutureTasks that compute a result that is to be displayed in a a table (never mind the GUI aspect here).

I have been looking at this for so long, I am beginning to doubt my sanity.

Consider this piece of code:

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
         ... 
      }
   }
   ...
}

When done() is called by an Executor handling an instance of MyTask, I check if I got there, because the task was cancelled. If so, I skip all remaining activities, especially I do not call sendMessage().

The documentation for FutureTask.done() says:

Protected method invoked when this task transitions to state isDone (whether normally or via cancellation). The default implementation does nothing. Subclasses may override this method to invoke completion callbacks or perform bookkeeping. Note that you can query status inside the implementation of this method to determine whether this task has been cancelled.
(API Reference)

But what I do not get from the documentation of FutureTask are the semantics while done() is being executed. What if I pass the isCancelled() check at the beginning, but right after that some other thread calls my cancel() method? Will that cause my task to change its mind and reply isCancelled() == true from then on?

If so, how would I later know if the the message was sent? Looking at isDone() would just tell me that execution of the task was finished, but as isCancelled() were true then as well, I could not know if it got to send the message out in time.

Maybe this is obvious, but I do not really see it right now.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

橘味果▽酱 2024-08-06 04:15:53

来自 API(强调我的):

公共布尔取消(布尔mayInterruptIfRunning)

从界面复制的描述:Future

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

因此,FutureTask 正在消除这样的假设:当任务转换到 isDone 阶段时,您无法取消任务。

From the API (emphasis mine):

public boolean cancel(boolean mayInterruptIfRunning)

Description copied from interface: Future

Attempts to cancel execution of this task. This attempt will fail if the task has already completed, already been cancelled, or could not be cancelled for some other reason.

So FutureTask is working off the assumption that you cannot cancel a task when it has transitioned to the isDone stage.

§普罗旺斯的薰衣草 2024-08-06 04:15:53

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

I suggest to write a small test case which allows you to call cancel() while your Future instance hangs in done() and see what happens.

天赋异禀 2024-08-06 04:15:53

为什么不根据 ExecutorService 返回的 Future对象的结果将消息发送到任务“外部”? 我已经使用了这种模式,它似乎工作得很好:通过 ExecutorService 提交一堆 Callable任务。 然后,对于每个主要任务,提交一个辅助任务,该任务等待主要任务的 Future,并仅当 Future<;V> 表示主要任务已成功完成。 这种方法无需猜测。 当对 Future.get()的调用返回时,只要您不调用 get< 的版本,就可以保证任务已达到终止状态/strong> 需要一个超时参数。

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

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

Why not send the message "outside" of the task, based on the outcome of the Future<V> object returned by an ExecutorService? I've used this pattern and it seems to work well: Submit a bunch of Callable<V> tasks through an ExecutorService. Then, for each primary task, submit a secondary task that waits on the Future<V> of the primary task and does some follow-up action (like send a message) only if the Future<V> indicates that the primary task completed successfully. There is no guesswork with this approach. When the call to Future<V>.get() returns, you're guaranteed that the task has reached a terminal state, as long as you don't call the version of get that takes a timeout argument.

If you take this approach, you should use two separate ExecutorService instances: one for the primary tasks and one for the secondary ones. This is to prevent deadlocks. You don't want secondary tasks to start up and potentially block primary tasks from starting when the thread pool size is limited.

There's no need to extend FutureTask<V> at all. Just implement your tasks as Callable<V> objects. But if for some reason you want to detect if the task was canceled from within the Callable<V> code, just check the interrupt status of the thread with Thread.interrupted().

最佳男配角 2024-08-06 04:15:53

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

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

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

FutureTask#done() is called no more than once for any given instance, and it's only called for one reason -- run() completed either with or without error, or cancel() ran before either of the preceding events occurred. The record of completion by any of these outcomes is latching. The reason a FutureTask completed can't change, regardless of competing events seemingly happening "at the same time."

Hence, within FutureTask#done() only one of isCancelled() or isDone() will return true then and forever more. It's difficult to distinguish between isDone() reporting true by way of error or successful completion. You can't override set() or setException(Throwable) decisively, as both delegate to the inner AQS to decide whether the attempt to record a successful yielding of a value or encountering an exception should stick. Overriding either method only lets you know that it was called, but you can't observe the decision made by the base implementation. If either event occurs "too late"—say, after cancellation—the attempt to record the value or the exception will be ignored.

Studying the implementation, the only way I see to discern a non-canceled successful outcome from an error is to bite the bullet and call get().

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文