Каково ожидаемое поведение потока приложения JavaFX? - PullRequest
0 голосов
/ 06 сентября 2018

Реализация X EventHandler присоединена к Панели и слушает все MouseEvents. Конечно, X имеет метод handle(), который получает MouseEvents из потока приложений JavaFX.

Панель содержит прямоугольник. Когда панель получает MouseEvent.MOUSE_CLICK на прямоугольник, X делает две вещи:

  1. Удаляет прямоугольник с панели, затем сразу добавляет еще один (это может вызвать дополнительные события.

  2. продолжается некоторая произвольная обработка

Вот вопрос:

Ожидается ли обработка на шаге 2 до до до каких-либо дальнейших событий, передаваемых в X через handle() Потоком приложений JavaFX? Отмечая, что Шаг 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 ]

0 голосов
/ 21 сентября 2018

Я потерял свой предыдущий логин, но я являюсь ОП этой темы. Просто подумал, что я обновлю его, чтобы сообщить, что проблема здесь реальная, подтвержденная, и ей был присвоен идентификатор ошибки в Oracle, и вы можете сослаться на нее здесь:

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

Спасибо всем, кто привлек меня к этому! Надеюсь, это поможет тому, кто с этим столкнется.

0 голосов
/ 07 сентября 2018

Ожидается ли обработка на шаге 2 до того, как какие-либо дальнейшие события будут отправлены в X через handle () потоком приложений JavaFX?

Да. Так что поток JavaFX делает все последовательно. Например, если вы добавите Thread.sleep в ваш метод handle(), то поток JavaFX не будет делать ничего, пока не закончится сон. Он выполняет всю эту обработку последовательно, что, я думаю, является определяющим элементом потока. Он не отключается и не обрабатывает другие события параллельно. Это очень важно в анимации, потому что вся эта обработка должна выполняться до того, как поток JavaFX вычислит и отобразит следующий кадр.

ADDENDUM:

Учтите это - удаление Rectangle в результате MouseEvent отправляет событие MouseEvent.MOUSE_EXIT, если курсор находится над Rectangle, потому что в глазах JavaFX это то, что только что произошло. Этот MouseEvent генерируется в потоке приложений JavaFX и обрабатывается им. Теперь вот о чем подумать. Поток приложений JavaFX может либо выполнить, а затем MOUSE_EXIT обработать X () или продолжить произвольную обработку dcoig. Что это делает?

Событие MOUSE_CLICKED будет обработано первым. После завершения потока, обрабатывающего все инициированные события, он выводится на экран. После завершения обновления экрана он будет обрабатывать любые новые инициированные события, такие как MOUSE_EXIT. Например, допустим, вы создали узел, который удаляет узел в MOUSE_ENTERED, а затем возвращает его в MOUSE_EXIT. Когда вы наводите указатель мыши на этот узел, он будет мигать с частотой кадров - вместо бесконечного цикла перед обновлением экрана.

...