Приложение зависает или "Не работает поток приложений FX" происходит во время активности приложения

Приложение реагирует на действия, которые происходят на геймпаде. Когда кнопка нажата, что-то происходит в пользовательском интерфейсе. Но я столкнулся с проблемой с зависанием приложения или исключением "java.lang.IllegalStateException: Not on FX application thread".

Чтобы исправить это, я попытался использовать следующие подходы: Platform.runLater() и Task. Но это не помогло.

Вот код проблемы:

public class GamepadUI extends Application{

    private static final int WIDTH = 300;
    private static final int HEIGHT = 213;

    private Pane root = new Pane();
    private ImageView iv1 = new ImageView();

    private boolean isXPressed = false;

    @Override
    public void start(Stage stage) throws Exception {
        initGUI(root);

        Scene scene = new Scene(root, WIDTH, HEIGHT);
        stage.setScene(scene);
        stage.setResizable(false);
        stage.show();
    }

    public void pressBtn() {
        if(!isXPressed) {
            iv1.setVisible(true);
            isXPressed = true;
        }
    }

    public void releaseBtn() {
        if(isXPressed) {
            iv1.setVisible(false);
            isXPressed = false;
        }
    }

    private void initGUI(final Pane root) {
        Image image = new Image(Props.BUTTON);
        iv1.setImage(image);
        iv1.setLayoutX(198);
        iv1.setLayoutY(48);
        iv1.setVisible(false);
        root.getChildren().add(iv1);

        runTask();
    }

    public void runTask() {
        Task task = new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                initStubGamepad();
                return null;
            }
        };

        new Thread(task).start();
    }

    public static void main(String[] args) {
        launch(args);
    }

    public void initStubGamepad() {
        Random rnd = new Random();
        try {
            while (true) {
                if (rnd.nextInt(30) == 3) {
                    pressBtn();
                } else if (rnd.nextInt(30) == 7) {
                    releaseBtn();
                }
            }
        } catch (Exception ex) {
            System.out.println("Exception: " + ex);
        }
    }
}

initStubGamepad() эмулирует просмотр активности кнопок геймпада. Когда пользователь нажимает любую кнопку (rnd.nextInt(30) == 3) - изображение появляется в пользовательском интерфейсе. Когда пользователь отпускает эту кнопку (rnd.nextInt(30) == 7) - изображение исчезает из пользовательского интерфейса.

В случае, если выше java.lang.IllegalStateException: Not on FX application thread. Если вы измените runTask() на что-то вроде этого:

Platform.runLater(new Runnable() {
    @Override
    public void run() {
        initStubGamepad();
    }
});

Затем приложение зависает или даже основной пользовательский интерфейс вообще не появится, но активность геймпада продолжается.

Я хочу просто показать/скрыть разные образы, когда какая-либо активность обнаружена на геймпаде (кстати, нет способа контролировать активность геймпада, кроме опроса геймпада в бесконечном цикле). Что я ошибся

Ответ 1

Объяснение

В первом сценарии, когда вы используете

Task task = new Task<Void>() {
  @Override
  protected Void call() throws Exception {
      initStubGamepad();
      return null;
  }
}

Внутри initStubGamepad(), который работает с Задачей, вы пытаетесь обновить компоненты пользовательского интерфейса внутри методов pressBtn() и releaseBtn(), поэтому вы сталкиваетесь с

java.lang.IllegalStateException: Not on FX application thread

потому что все обновления пользовательского интерфейса должны появляться в потоке JavaFX

В втором сценарии, когда вы используете

Platform.runLater(new Runnable() {
    @Override
    public void run() {
        initStubGamepad();
    }
});

пользовательский интерфейс не появляется, поскольку в initStubGamepad() имеется бесконечный цикл, который помещает поток приложений JavaFX в бесконечный цикл

Решение

К тому времени, как вы сюда доберетесь, вы, должно быть, уже нашли решение. Если вы этого не сделали, попробуйте поместить обновление компонентов Javafx в поток пользовательского интерфейса. Итак, вместо вызова initStubGamepad() внутри Platform.runLater попробуйте позвонить pressBtn() и releaseBtn() внутри него.

Попробуйте использовать

while (true) {
   if (rnd.nextInt(30) == 3) {
      Platform.runLater(() -> pressBtn());            
   } else if (rnd.nextInt(30) == 7) {
       Platform.runLater(() -> releaseBtn());            
   }
}

или вы также можете использовать

public void pressBtn() {
    if(!isXPressed) {
        Platform.runLater(() -> iv1.setVisible(true));
        isXPressed = true;
    }
}