2015-10-13 2 views
3

Я пытаюсь обеспечить обратную связь в приложении JavaFX 8, когда пользователь выбирает пункт меню, который запускает процесс блокировки в другом потоке. В моем реальном приложении это загрузка файла, но я создал тестовый пример с использованием минимального кода в качестве примера:Почему поток блокирует мою тему JavaFX UI?

import javafx.application.Application; 
import javafx.application.Platform; 
import javafx.scene.Scene; 
import javafx.scene.control.MenuButton; 
import javafx.scene.control.ToolBar; 
import javafx.scene.control.MenuItem; 
import javafx.stage.Stage; 

public class BlockingThreadTestCase extends Application { 
    public static void main(String[] args) { 
     launch(args); 
    } 

    @Override 
    public void start(Stage primaryStage) throws Exception { 
     MenuItem menuItem = new MenuItem("Start"); 
     MenuButton menuButton = new MenuButton(); 
     menuButton.setText("Async Process"); 
     menuButton.getItems().addAll(menuItem); 

     menuItem.setOnAction(event -> { 
      menuButton.setText("Running..."); 

      Platform.runLater(() -> { 
       try { 
        // Simulate a blocking process 
        Thread.sleep(5000); 
       } catch (Exception e) { 
        e.printStackTrace(); 
       } 

       menuButton.setText(menuButton.getText() + "Done!"); 
      }); 
     }); 

     final ToolBar toolbar = new ToolBar(menuButton); 
     final Scene scene = new Scene(toolbar); 
     primaryStage.setScene(scene); 
     primaryStage.setWidth(150); 
     primaryStage.show(); 
    } 
} 

Вот как это должно работать: При выборе пункта меню «Пуск», главном текст меню должен немедленно измениться на «Запуск ...», а затем он должен добавить «Готово!». после 5-секундного сна, который имитирует загрузку моего файла.

Что на самом деле происходит, как текст обновления стрельбы после блокирующий процесс делается, несмотря на то, я использую Platform.runLater(). Что я делаю не так?

+2

'Platform.runLater' будет выполнять блок« Runnable »в разделе« Dispatching Thread », что означает, что он заблокирует ваш пользовательский интерфейс. Это не то, как вы хотите отсрочить обратные вызовы, я не знаком с JavaFX, но вы хотите, чтобы какой-то «таймер» мог быть выполнен (с задержкой в ​​5 секунд) и который при запуске может обновить состояние вас меню в контексте EDT – MadProgrammer

+0

Используйте [Задача] (https://docs.oracle.com/javase/8/javafx/interoperability-tutorial/concurrency.htm), запущенную в новом потоке, который вы создаете. – jewelsea

+0

Я попытался создать свой собственный поток, но затем я получаю сообщение об ошибке при попытке обновить текст меню, потому что изменения интерфейса JavaFX могут быть сделаны только из основного потока пользовательского интерфейса. –

ответ

6

Самый простой способ сделать это - использовать Task. Platform.runLater необходим только в том случае, если вам необходимо обновить пользовательский интерфейс из другого потока и, следовательно, не обязательно в вашем случае. Если вы хотите отслеживать прогресс фоновой задачи во время ее работы, вы можете использовать методы updateMessage и updateProgress, чтобы безопасно передавать сообщения в поток пользовательского интерфейса, не беспокоясь о планировании через EDT. Вы можете найти более подробную информацию об этом здесь https://docs.oracle.com/javase/8/javafx/interoperability-tutorial/concurrency.htm.

См. Минимальный рабочий пример ниже.

import javafx.application.Application; 
import javafx.concurrent.Task; 
import javafx.scene.Scene; 
import javafx.scene.control.MenuButton; 
import javafx.scene.control.MenuItem; 
import javafx.scene.control.ToolBar; 
import javafx.stage.Stage; 


public class BlockingThreadTestCase extends Application { 
    public static void main(String[] args) { 
     launch(args); 
    } 

    @Override 
    public void start(Stage primaryStage) throws Exception { 
     MenuItem menuItem = new MenuItem("Start"); 
     MenuButton menuButton = new MenuButton(); 
     menuButton.setText("Async Process"); 
     menuButton.getItems().addAll(menuItem); 

     menuItem.setOnAction(event -> { 
      menuButton.setText("Running..."); 

      Task task = new Task<Void>() { 
       @Override 
       public Void call() { 
        //SIMULATE A FILE DOWNLOAD 
        try { 
         Thread.sleep(5000); 
        } catch (InterruptedException e) { 
         e.printStackTrace(); 
        } 
        return null; 
       } 
      }; 
      task.setOnSucceeded(taskFinishEvent -> menuButton.setText(menuButton.getText() + "Done!")); 
      new Thread(task).start(); 
     }); 

     final ToolBar toolbar = new ToolBar(menuButton); 
     final Scene scene = new Scene(toolbar); 
     primaryStage.setScene(scene); 
     primaryStage.setWidth(150); 
     primaryStage.show(); 
    } 
} 
+0

СПАСИБО! Я прошел через несколько итераций, и я ** попробовал 'Задачу', но мне не хватало' setOnSucceeded() 'часть. Это был мой недостающий кусок. –

+2

В долгоживущем приложении было бы лучше использовать Executors вместо явной реализации Thread. – ursa

+2

Чтобы реализовать предложение ursa, вы можете использовать [Сервис] (https://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Service.html) вместо задачи. Служба имеет некоторые встроенные средства для управления потоками через исполнителей. Вы также можете использовать Executors with Tasks, хотя API-интерфейс Task не предоставляет какой-либо вспомогательный API для этого. Использование службы немного сложнее, чем задача. – jewelsea

Смежные вопросы