JavaFX 应用程序线程的预期行为是什么?



EventHandler 实现 X 附加到 Pane 并侦听所有 MouseEvents。当然,X 有一个handle()方法,它从 JavaFX 应用程序线程接收 MouseEvents。

窗格包含一个矩形。当窗格收到矩形上的MouseEvent.MOUSE_CLICK时,X 会执行两项操作:

  1. 从窗格中删除矩形,然后立即添加另一个矩形(这可能会导致其他事件。

  2. 继续进行一些处理

问题来了:

步骤 2 中的处理是否应在 JavaFX 应用程序线程通过handle()将任何其他事件提交到 X 之前完成?请注意,步骤 1 可能会触发其他事件!

只是寻找是或否的答案。你回答背后的推理也会很好!

我应该补充一点,任何地方都没有涉及任何类型的其他线程,包括在"任意处理"中。


编辑:

示例代码

package bareBonesJavaFXBugExample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
/**
* An {@link Application} with one {@link Pane} containing one {@link Label}.
* The {@link Label} has a single {@link javafx.event.EventHandler}, 
* {@link LabelEventHandler} which processes all {@link MouseEvent}s the {@link Label}
* receives.
* 
* To trigger the bug, run the application, then spend a second mouse over the 
* little label in the upper left hand corner of the screen. You will see output to 
* standard I/O. Then, click the label, which will then disppear. Check the I/O for
* Strings ending in debugCounter is 1. 
* 
* What that String means and how it proves that the JavaFX Application Thread has 
* become reentrant is explained in the javadoc of {@link LabelEventHandler}.
*/
public class JavaFXAnomalyBareBonesApplication extends Application
{
public void start(Stage primaryStage)
{
Pane mainPane = new Pane();
mainPane.setMinHeight(800);
mainPane.setMinWidth(800);
Label label = new Label(" this is quite a bug !!!!");
LabelEventHandler labelEventHandler = new LabelEventHandler(mainPane, label);
label.addEventHandler(MouseEvent.ANY, labelEventHandler);
mainPane.getChildren().add(label);
Scene scene = new Scene(mainPane);
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* The entry point of application.
*
* @param args
*         the input arguments
*/
public static void main(String[] args) {
launch(args);
}
}

这是它唯一的依赖项,EventListener 类。我包含了足够的javadoc,使程序有意义。:


package bareBonesJavaFXBugExample;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import java.util.Collection;
import java.util.ConcurrentModificationException;
/**
* An {@link EventHandler} implementation for {@link MouseEvent}s.
* This implementation's {@link EventHandler#handle(Event)} shows the
* relevant debug information to standard output before and after removing
* the member {@link #label} from the {@link #pane}.
*
* <b>discussion</b><br></br>
* <p>
* Users should first satisfy themselves that the value of
* {@link LabelEventHandler#debugCounter} can only be non-zero, in fact 1
* (one) in the method {@link LabelEventHandler#showDebugInformation(String)}
* if the method {@link LabelEventHandler#handle(MouseEvent)}  has been
* re-entered recursively, that is, before a previous invocation of
* {@link LabelEventHandler#handle(MouseEvent)} has returned.
* <p>
* Proof:
* 1) <code>debugCounter</code> starts at value 0 (zero).
* 2) <code>debugCounter</code> is only incremented once, by 1 (one), and that
*    is after the first call to {@link LabelEventHandler#showDebugInformation(String)}
*    has returned.
* 3) <code>debugCounter</code> is only decremented once, by 1 (one) and that
*    is before the last call to {@link LabelEventHandler#showDebugInformation(String)}.
* 4) however, because <code>debugCounter</code> is a class variable
*    (it's static), if handle() is recurvsively re-entered then it's
*    value can be 1 (one) when the re-entrant
*
* Thread executes {@link LabelEventHandler#showDebugInformation(String)}
*
* End proof.
*
* The output of this method to standard I/O is volumnious but searching the
* output for the exact String "debugCounter is 1" will immediately show the
* {@link LabelEventHandler#handle(MouseEvent)} method to have been recursively
* entered.
*
* Some other possibilities other than the JavaFX Application Thread recursing
* into {@code handle()} need to be addressed.
* One is the fact that the compiler is free to reorder statements if it can
* prove that such a reordering would have no effect on the program's correctness.
*
* So somehow the compiler is reordering the increment/decrement of
* {@code  debugCounter} and the calls to {@code   showDebugInformation}.
* But this would alter the correctness of the program, so this cannot be the case,
* or the compiler is making an error.
*
* Another is the fact that I/O is not instantaneous and can appear to standard
* output later than it actually was executed.
* This is something often seen in debug stack traces, where the output is
* broken up  or interleaved by the output of the stack trace even though the
* two sets of statments, i/o and stack trace i/o, were strictly ordered in execution.
* But this can't account for the value of {@code   debugCounter}, so it can't
* be the reason "debugCounter is 1" appears in output.
*
* In fact we can make this recursive behaviour more obviously consequential
* to the correctness of the program. If {@code   handle() } is being
* recursively re-entered, then we can force a
* {@link ConcurrentModificationException} on a {@link Collection}.
* If we try to invoke {@link Collection#add(Object)} to a {@link Collection}
* while it is being iterated through, then a {@link ConcurrentModificationException}
* will be thrown.
*
* If we re-write this program slightly to first add or remove to or from a
* {@link Collection} then iterate through that {@link Collection} within the
* scope of  execution of {@code   handle()}, <em>and</em> {@code   handle()}
* is being recursively invoked, then we may see a {@link ConcurrentModificationException}.
*
* Two other instances of this same basic program exist at the link provided.
* They are named {@link JavaFXAnomalySimpleVersionApplication} and
* {@link JavaFXAnomalyComplexVersionApplication} which is written to throw a
* {@link ConcurrentModificationException} when the JavaFX Application Thread
* becomes reentrant.
*
* I also have a screen grab (not included here) of the stack trace at a
* specific moment <code>handle()/code> is being invoked, and it can clearly
* be seen that the previous executing line was within the scope of execution
* of the previous invocation of <code>handle()</code>.
*
* In the .zip file at the link there is a readme.txt. In that file.
* I present the two lines of code which need to be added, and where
* they need to be added,  so as to generate the same stack trace
* showing the same thing.
*/
public class LabelEventHandler implements EventHandler<MouseEvent> {
/**
* a counter which acts as a recursion detector.
* If {@link #handle(MouseEvent)} is never recursively invoked by
* the JavaFX Application Thread, then it's value will never be other
* than 0 (zero) in {@link #showDebugInformation(String)}.
*/
private static int debugCounter;
/**
* The {@link Label} which will disappear when clicked. This causes
* a MOUSE_EXITED_TARGET event top be fired and that in turn causes
* the JavaFX Event Dispatch Thread to recurse into this class's
* {@link #handle(MouseEvent)}
*/
private Label label;
/**
* The {@link Pane} which contains the {@link Label}. The
* {@link Label} is removed from this {@link Pane}.
*/
private final Pane pane;
/**
* Assign the values to the members {@link Pane} and {@link Label}
*/
public LabelEventHandler(Pane pane, Label label) {
this.pane = pane;
this.label = label;
}
/**
* Causes the member {@link #label} to be removed as a child of the
* member {@link #pane}.
*
* @param mouseEvent the {@link MouseEvent} received from the
* JavaFX Application Thread from the {@link Label} which this
* {@link EventHandler} is listening to.
*/
@Override
public void handle(MouseEvent mouseEvent) {
// debug can only every be 0 (zero) at this point
showDebugInformation("ENTERING");
debugCounter++;

if (mouseEvent.getEventType().equals(MouseEvent.MOUSE_PRESSED)
&& mouseEvent.isPrimaryButtonDown()) {
pane.getChildren().remove(label);
}
debugCounter--;
// debug can only every be 0 (zero) at this point
showDebugInformation("EXITING");
}
/**
* Displays two values to standard output. The first is a
* {@link String}  indicating whether the
* {@link LabelEventHandler#handle(MouseEvent)} method is
* being entered or exited and the second is the value of
* {@link LabelEventHandler#debugCounter} at the time this
* method is executed.
*
* @param enterOrExit the string ENTERING or EXITING
* reflecting the point  at which this method was invoked
* by {@link LabelEventHandler#handle(MouseEvent)}.
*/
private void showDebugInformation(String enterOrExit) {
System.out.println();
System.out.print(enterOrExit + " method handle");
System.out.print(" and debugCounter is " + debugCounter);
System.out.println();
}
}

步骤 2 中的处理是否应该在 JavaFX 应用程序线程通过 handle(( 将任何进一步的事件提交到 X 之前完成?

是的。 因此,JavaFX 线程按顺序执行所有操作。 例如,如果要在handle()方法中添加Thread.sleep,则 JavaFX 线程在睡眠结束之前不会执行任何操作。 它按顺序处理所有内容,我想这是线程的定义元素。 它不会关闭并并行处理其他事件。 这在动画中非常重要,因为所有这些处理都必须在 JavaFX 线程计算和显示下一帧之前进行。

补遗:

考虑一下 - 如果光标位于矩形上,则由于 MouseEvent 而删除矩形会发布MouseEvent.MOUSE_EXIT事件,因为在 JavaFX 眼中,这就是刚刚发生的事情。该 MouseEvent 既在 JavaFX 应用程序线程上生成,又将由它处理。现在要考虑的事情。JavaFX 应用程序线程可以跟进并MOUSE_EXIT X 的 handle(( 或继续 dcoig 仲裁处理。它做什么?

MOUSE_CLICKED事件将首先得到处理。 线程处理完所有触发的事件后,将绘制到屏幕。 更新屏幕完成后,它将处理任何新的触发事件,例如MOUSE_EXIT。 例如,假设您创建了一个节点,该节点删除了MOUSE_ENTERED上的节点,然后将其放回MOUSE_EXIT上。 当您将鼠标移到此节点上时,它将以帧速率闪烁 - 而不是在更新屏幕之前进入无限循环。

我丢失了以前的登录名,但我是这个线程的 OP。只是想我会更新它以报告此处的问题真实存在,已确认,并且已在 Oracle 上提供了错误 ID,您可以在此处引用它:

https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8211000

感谢所有参与我的人!希望它能帮助其他人遇到它。

最新更新