使用 2 个按钮启动和停止 JavaFX 后台线程

Start and stop JavaFX background thread with 2 buttons

我正在尝试做一个简单的 UI 来启动一个 selenium 测试,该测试能够启动一个后台线程,该线程在按下开始按钮时启动浏览器并停止线程并在按下时关闭它停止按钮被按下。 不幸的是,当我在启动后单击停止时,它不起作用。如果我让它完成,我将无法重新启动线程。我将如何更新它,以便我可以提交一个可以通过停止按钮停止的新线程。

        package application;

import org.openqa.selenium.WebDriver;

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;


public class Main extends Application {
    Stage window;
    GridPane grid;
    public void start(Stage primaryStage) {

        /*
         * Set up the stage
         */
        window = primaryStage;
        window.setTitle("URL LOADER - V1");
        grid = new GridPane();
        grid.setPadding(new Insets(10,10,10,10));
        grid.setVgap(8);
        grid.setHgap(10);
        window.setResizable(false);

        /*
         * URL input
         */
        Label URLLabel = new Label("URL");
        GridPane.setConstraints(URLLabel,0,0);
        TextField URLTextField = new TextField();
        URLTextField.setPromptText("https://www.google.com");
        GridPane.setConstraints(URLTextField,1,0);

        /*
         * Create Buttons
         */
        Button buttonStart = new Button("Create");
        GridPane.setConstraints(buttonStart,1,6);
        Button buttonStop = new Button("Stop");
        GridPane.setConstraints(buttonStop,1,8);

        grid.getChildren().addAll(URLLabel,URLTextField, buttonStart, buttonStop);

        /*
         * Create the scene
         */
        Scene scene = new Scene(grid, 300, 300);
        window.setScene(scene);
        window.show();

        Task<Void> task = new Task<Void>(){
            @Override
            protected Void call() {
                new VisitPage().Start(this,URLTextField.getText());;
                return null;
            }
        };

        buttonStart.setOnAction(new EventHandler<ActionEvent>() {
        /*
         * Start Button Clicked
         */
        public void handle(ActionEvent event) {

            new Thread(task).start();

        }

    });

        buttonStop.setOnAction(new EventHandler<ActionEvent>() {
            /*
             * Start Button Pressed
             */
            public void handle(ActionEvent event) {
                System.out.println("Stop Pressed");

            }
        });

    }

    public class VisitPage {
        private String URL;
        Browser BrowserFactory;
        ThreadLocal<WebDriver> drivers;
        WebDriver Browser;
        public void Start(Task<Void> task, String URL) {
            while (true) {
                if (task.isCancelled())
                {
                        System.out.println("Canceling...");
                        System.out.println("Stop Pressed");
                        Browser.close();
                        Browser.quit();
                        BrowserFactory.CloseDriver(drivers);
                        task.cancel();
                }
                else
                {
            /*
             * Create Browser Factor to make ThreadLocal Browsers
             */
            BrowserFactory = new Browser(1, 1);
            drivers = BrowserFactory.SpawnBrowser();
            /*
             * Grab a Browser
             */
            Browser = BrowserFactory.SpawnDriver(drivers);
            /*
             * Visit and scrape
             */
            Browser.get(URL);
            /*
             * Wait 5 Seconds before closing
             */
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            Browser.close();
            Browser.quit();
            BrowserFactory.CloseDriver(drivers);
                 }

             }
        }

    }


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

根据documentation

As with FutureTask, a Task is a one-shot class and cannot be reused. See Service for a reusable Worker.

因此您必须为每个 运行 创建新任务。所以我在 Main:

中添加了任务作为字段
Stage window;
GridPane grid;
Task<Void> task;

然后在单击开始按钮时创建任务:

buttonStart.setOnAction(new EventHandler<ActionEvent>() {
    /*
     * Start Button Clicked
     */
    @Override
    public void handle(ActionEvent event) {
        if(task != null) {
            System.out.println("Task already running");
            return;
        }
        task = new Task<Void>() {
            @Override
            protected Void call() {
                new VisitPage().start(this, URLTextField.getText());
                ;
                return null;
            }
        };
        Thread thread = new Thread(task);
        thread.setDaemon(true);
        thread.start();
    }
});

单击停止按钮必须取消任务:

buttonStop.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent event) {
            if(task == null) {
                System.out.println("Task not running");
                return;
            }
            System.out.println("Stop Pressed");
            task.cancel();
            task = null;
        }
    });

这不会执行任何操作,因为您有责任在任务取消时结束任务,而您并没有结束无限循环。 所以你的 VisitPage 应该看起来像这样(我跳过了测试细节,因为我没有在类路径上):

public class VisitPage {
    public void start(Task<Void> task, String URL) {
        while (!task.isCancelled()) {
            System.out.println("Running test");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("Test run ended");
        }
        System.out.println("Canceling...");
        System.out.println("Stop Pressed");
        return;
    }
}

一些小问题:

从技术上讲,task.cancel() 有时会结束您的线程,如果您不捕获 InterruptedException 线程正在休眠时抛出的 InterruptedException

我不确定你的代码是如何编译的,但我必须将一些变量设为 final 以便它们可以在处理程序中使用:(没关系,来自 Java SE 8 的局部变量可以有效地成为 final)

final TextField URLTextField = new TextField();
//...
final  Task<Void> task = new Task<Void>(){
//...

我会将创建的线程定义为守护进程,这样当您关闭 UI 而不停止测试时它不会保持 运行ning:

Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();

我还将 Start 方法重命名为 start