Java 5:java.util.concurrent.FutureTask - cancel()和done()的语义
我目前正在使用 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
来自 API(强调我的):
因此,FutureTask 正在消除这样的假设:当任务转换到 isDone 阶段时,您无法取消任务。
From the API (emphasis mine):
So FutureTask is working off the assumption that you cannot cancel a task when it has transitioned to the isDone stage.
我建议编写一个小测试用例,允许您在
Future
实例挂在done()
中时调用cancel()
并查看会发生什么。I suggest to write a small test case which allows you to call
cancel()
while yourFuture
instance hangs indone()
and see what happens.为什么不根据 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().
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, orcancel()
ran before either of the preceding events occurred. The record of completion by any of these outcomes is latching. The reason aFutureTask
completed can't change, regardless of competing events seemingly happening "at the same time."Hence, within
FutureTask#done()
only one ofisCancelled()
orisDone()
will return true then and forever more. It's difficult to distinguish betweenisDone()
reporting true by way of error or successful completion. You can't overrideset()
orsetException(Throwable)
decisively, as both delegate to the innerAQS
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()
.