2016-01-25 4 views
2

Похоже, существует общее понимание того, что поток пользовательского интерфейса JavaFX поддерживается 60 обновлениями в секунду (1, 2). Я понимаю, что обновление означает pulse.Крышка JavaFX FPS при 60 FPS

Импульс представляет собой событие, которое указывает на графе сцены JavaFX, что является время, чтобы синхронизировать состояние элементов на графе сцены с Prism. Импульс сжимается со скоростью 60 кадров в секунду (fps) максимум и запускается всякий раз, когда анимация выполняется на графике сцены. Даже , когда анимация не работает, импульс запланирован, когда что-то в изменяется график сцены. Например, если позиция кнопки изменена, запланирован импульс.

Источник: https://docs.oracle.com/javafx/2/architecture/jfxpub-architecture.htm

Чтобы выяснить, что происходит, если есть, например, более 60 звонков в Platform.runLater Я написал эту небольшую программу:

import javafx.application.Application; 
import javafx.application.Platform; 
import javafx.scene.Scene; 
import javafx.scene.layout.StackPane; 
import javafx.stage.Stage; 

import java.text.SimpleDateFormat; 
import java.util.Date; 
import java.util.Timer; 
import java.util.TimerTask; 

public class HighUITraffic extends Application { 

    private int counter = 0; 
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); 

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

    @Override 
    public void start(Stage primaryStage) { 


     StackPane root = new StackPane(); 


     Timer timer = new Timer(); 
     TimerTask task = new TimerTask() { 
      @Override 
      public void run() { 
       long sheduleTime = System.currentTimeMillis(); 
       System.out.println("Task "+counter+" run at "+sdf.format(new Date(sheduleTime))); 
       Platform.runLater(new RunInUI(counter)); 
       counter++; 
      } 
     }; 
     timer.schedule(task, 0, 10); // every 10ms => 100 per second 

     Scene scene = new Scene(root, 300, 250); 

     primaryStage.setTitle("Hello World!"); 
     primaryStage.setScene(scene); 
     primaryStage.show(); 
    } 
    private static class RunInUI implements Runnable { 
     private final int counter; 

     public RunInUI(int counter) { 
      this.counter = counter; 
     } 

     @Override 
     public void run() { 
      long executionTime = System.currentTimeMillis(); 
      System.out.println("Task "+counter+" executed in Application Thread at "+sdf.format(new Date(executionTime))); 
     } 
    } 
} 

Мои ожидания были что либо:

  • Runnable s уложены в стопку, а поток пользовательского интерфейса перемещается, а затем выполняет все Runables, которые поставили в очередь.
  • Поток пользовательского интерфейса выполняет первые 60 вызовов, а затем очереди Runable.

Однако то, что произошло после того, как какой-то первоначальной задержки, где Runables очередями и где потом все обрабатывается за один проход с помощью потока пользовательского интерфейса, в том, что два потока выполняются в порядке:

Task 1281 работать на 2016-01-25T18: 37: 00.269 Задача 1281, выполненная в . Тема приложения на 2016-01-25T18: 37: 00.269 Задача 1282, выполняемая в 2016-01-25T18: 37: 00.274 Задача 1282, выполненная в прикладной теме на этапе 2016- 01-25T18: 37: 00.274 Задача 1283 запускается в 2016-01-25T18: 37: 00.279

И было более 60 вызовов нитей в течение одной секунды (я пробовал с 100 и 200, без каких-либо различий).

Поэтому я смущен:

  • ли я неправильно понять концепцию укупорки на 60 импульсов?
  • Этот небольшой фрагмент выполнения не так сильно нагружен, чтобы предел мог быть превышен?

Что я хотел знать в первую очередь, так это то, что происходит с Runable s, которые нажимаются на поток пользовательского интерфейса, если поток достиг предела лимита. Все ли Runable s, которые поставили в очередь, выполнялись последовательно в одном прогоне потока пользовательского интерфейса или выполнялись только один Runable, а остальное пришлось ждать? Второй случай вызовет серьезные проблемы, когда непрерывно нажимает больше Runable с на поток пользовательского интерфейса, чем этот предел.

ответ

5

Пульсы ограничены 60 кадрами в секунду.Runnable s, поданных в приложение приложения FX, нет.

Как я понимаю, существуют два потока: поток приложения эффектов и поток рендеринга призмы. Тема приложения FX потребляет Runnable s из очереди и выполняет их. Поток рендеринга призмы выполняет до 60 раз в секунду, синхронизирует с потоком приложения FX (тем самым временно предотвращая его выполнение новых runnables) и рендеринг кадра, а затем освобождая Thread приложения FX.

Таким образом, поток приложения FX будет выполнять ваш Runnable, как только это возможно, и не ограничивается одним запуском каждые 1/60s; но в процессе рендеринга фрейма он не будет запускать новую очередь из очереди. (Пользовательские события обрабатываются в приложении приложения FX таким же образом, как и сообщение Runnable.)

Таким образом, существует несколько способов сделать плохие вещи. Один из них - иметь код в потоке приложения FX (будь то в обработчике события или в Runnable, отправленном на Platform.runLater()), который занимает много времени. Это приведет к блокировке потока рендеринга призмы при синхронизации с потоком приложения FX, что означает, что следующий кадр не может быть отображен до завершения выполнения. Другим является бомбардировка потока приложений FX слишком большим числом Runnable, так что очередь растет быстрее, чем они могут быть использованы. (По сути, поскольку в очереди всегда есть что-то доступное, поток приложений FX становится занятой нитью в этих обстоятельствах, и нет гарантии, что нить рендеринга призмы никогда не будет запущена.)

Таким образом, прямой ответ на ваш вопрос что Runnable s выполняются в том порядке, в котором они размещены, так быстро, как только они могут быть в одном потоке. Отрисовка кадра, которая ограничена 60 кадрами в секунду, временно остановит потребление Runnable s из очереди.

В псевдокоде, я догадка (только моя интуиция о том, как это работает, это не основано на исходном коде) FX Применение Нить выглядит как

while (fxToolkitRunning()) { 
    Runnable runnable = runnableQueue.take(); // block until something available 
    acquireLock(); 
    runnable.run(); 
    releaseLock(); 
} 

и рендеринг призмы нить выглядит примерно так:

while (fxToolkitRunning()) { 
    while (! timeForNextFrame()) { 
     sleep(timeUntilNextFrame()); 
    } 
    acquireFXApplicationThreadLock(); 
    if (sceneNeedsUpdating()) { 
     renderFrame(); 
    } 
    releaseFXApplicationThreadLock(); 
} 
Смежные вопросы