Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
803 views
in Technique[技术] by (71.8m points)

java - JavaFX Task ending and JavaFX threading

I only just started learning about JavaFX today and I was trying to learn more about it by making a Snake clone, but I am having trouble with threading. I wanted to create a thread that updated the snake's position on the screen, but couldn't use one in a normal Runnable thread way, because I was using JavaFX from within that thread to update positions of rectangles drawn to the screen (Which I learned you cannot do, and must instead use Tasks, Services, Platform.runLater, etc?) My class that I was creating the thread from extends JavaFX.scene.layout.Pane, and I am trying to use a task to update the snake position. My problem is: The task only seems to run once or twice and quits, without me giving any interrupts.

Constructor for the class that extends Pane (The Snake class extends Group):

public GameFrame(){
    this.setPrefSize(800, 600);

    Snake snake = new Snake();
    this.getChildren().add(snake);

    taskThread = new Thread(new Task<Void>() { 
        protected Void call() throws Exception {
                while(!Thread.currentThread().isInterrupted()){
                    snake.updatePosition();
                    try{
                    Thread.sleep(1000);
                    } catch(InterruptedException e){
                        break;
                    }
                }
            return null;
        }
    });
    taskThread.start();
}

I feel like I don't actually grasp what the best thing to do here is, and what I am trying to do may be hackish. Are there any suggestions on what I should do, or how I could fix this?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The basic rules for threading in JavaFX (and forgive me if you already understand some of this, I just want to be complete) are:

  1. Anything that blocks execution (or takes a long time to execute) should be run on a background thread - not on the FX Application Thread
  2. Anything that changes the state of a Node that is part of the scene graph should be executed on the FX Application Thread

In order to help achieve these, the JavaFX API provides a Task class. This has a call() method that returns a value; it is a Runnable so can be provided as the argument to a Thread constructor, or passed to an Executor. It also provides useful callbacks that are guaranteed to be executed on the FX Application Thread, such as setOnSucceeded, setOnFailed, and various update...() methods that update properties such as progress and message on the FX Application Thread.

However, the Task class is really designed for one-off tasks: think about applications that need to retrieve data from a database, for example, which may take time. These execute a specific action and return a result. Your case is somewhat different, in that your thread is executing continuously.

In this case, it's probably better to use a simple Thread and use Platform.runLater(...) to update the UI. Platform.runLater(...) takes a Runnable and executes its run() method on the FX Application Thread.

It's not clear to me why your code is behaving the way you describe, but assuming the method call snake.updatePosition() causes a change in the UI, that should be executed on the FX Application Thread. In any case I would try

taskThread = new Thread(new Runnable() { 
    public void run() {
            while(!Thread.currentThread().isInterrupted()){
                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        snake.updatePosition();
                    }
                });
                try{
                    Thread.sleep(1000);
                } catch(InterruptedException e){
                    break;
                }
            }
    }
});

If you are using Java 8, this all looks a lot nicer with lambdas replacing the anonymous inner classes:

taskThread = new Thread( () -> {
    while (! Thread.currentThread().isInterrupted()) {
        Platform.runLater( snake::updatePosition );
        try {
            Thread.sleep(1000);
        } catch (InterruptedException exc) {
            break ;
        }
    }
});

One other technique in JavaFX for executing something periodically is to (ab?)use an Animation:

    Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1), 
        new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                snake.updatePosition();
            }
        }
    ));
    timeline.setCycleCount(Animation.INDEFINITE);
    timeline.play();

or, in Java 8, the somewhat slick

    Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1), 
        event -> snake.updatePosition()));
    timeline.setCycleCount(Animation.INDEFINITE);
    timeline.play();

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...