正确使用JavaFX任务执行多线程和线程池

正确使用JavaFX任务执行多线程和线程池,java,multithreading,javafx,Java,Multithreading,Javafx,我有一个选项,用户可以从文件选择器提交多个文件,由一些代码处理。结果将是读取文件的IO,然后是存储数据的实际繁重计算。用户可以选择多个文件,而且由于文件处理不依赖于所选的任何其他文件,这使得我的生活更容易通过线程处理 此外,用户需要一个按钮列表,每个要取消的任务一个按钮,以及一个“全部取消”按钮。因此,我必须考虑有选择地或集体地杀死一个或所有任务的能力。 最后一个要求是,我不会让用户打开大量文件而阻塞系统。因此,我认为线程池的线程数是有限的(假设对于任意数量的线程,我将其限制为4) 我不确定如何

我有一个选项,用户可以从文件选择器提交多个文件,由一些代码处理。结果将是读取文件的IO,然后是存储数据的实际繁重计算。用户可以选择多个文件,而且由于文件处理不依赖于所选的任何其他文件,这使得我的生活更容易通过线程处理

此外,用户需要一个按钮列表,每个要取消的任务一个按钮,以及一个“全部取消”按钮。因此,我必须考虑有选择地或集体地杀死一个或所有任务的能力。

最后一个要求是,我不会让用户打开大量文件而阻塞系统。因此,我认为线程池的线程数是有限的(假设对于任意数量的线程,我将其限制为4)

我不确定如何正确地设置这一切。我有我需要做什么的逻辑,但使用正确的类是我遇到的问题

我已经查过了,所以如果答案不知怎么在里面,那我就误读了这篇文章

  • 有没有JavaFX类可以帮助我解决这种情况

  • 如果不是,我将如何将任务与某种线程池混合?我是否必须创建自己的线程池,或者是否已经为我提供了一个线程池

  • 我是否要在某个地方创建一个包含我愿意允许用户使用的最大线程数的单例

我更愿意使用Java库中已有的一个,因为我不是一个多线程专家,我担心我可能会做错。由于线程bug似乎是这个星球上最邪恶的调试对象,我正努力确保尽可能正确地进行调试

如果没有办法做到这一点,而我必须推出自己的实现,那么最好的方法是什么


编辑:我应该注意,我对线程基本上是新手,我以前使用过线程,我正在阅读关于线程的书籍,但这将是我第一次主要使用线程,我真的很想正确地使用它。

JavaFX有一个
JavaFX.concurrent
API;特别是,该类非常适合您的用例。此API旨在与
java.util.concurrent
API一起工作。例如,
Task
是的一个实现,因此可以将其提交给。当您想要使用线程池时,您可以创建一个
Executor
,它为您实现线程池,并将您的任务提交给它:

final int MAX_THREADS = 4 ;

Executor exec = Executors.newFixedThreadPool(MAX_THREADS);
由于这些线程在UI应用程序的后台运行,您可能不希望它们阻止应用程序退出。通过使executor守护程序线程创建的线程:

Executor exec = Executors.newFixedThreadPool(MAX_THREADS, runnable -> {
    Thread t = new Thread(runnable);
    t.setDaemon(true);
    return t ;
});
生成的执行器将有一个最多包含
MAX_线程的池。如果在没有可用线程的情况下提交任务,它们将在队列中等待,直到有线程可用

要执行实际的
任务
,需要记住以下几点:

不能从后台线程更新UI。由于您的
任务
已提交给上述执行者,因此它的
call()
方法将在后台线程上调用。如果在执行
调用
方法的过程中确实需要更改UI,可以在
Platform.runLater(…)
中包装更改UI的代码,但最好对其进行结构化,以避免出现这种情况。特别是,
任务
有一组
updatexx(…)
方法,用于更改FX应用程序线程上相应的
任务
属性的值。您的UI元素可以根据需要绑定到这些属性

建议
调用
方法不要访问任何共享数据(除了通过上述
updatexx(…)
方法之外)。仅实例化
Task
子类设置
final
变量,让
call()
方法计算一个值,然后返回值

为了取消
任务
任务
类定义了一个内置的
cancel()
方法。如果有一个长时间运行的
call()
方法,则应定期检查
isCancelled()
的值,如果返回
true
,则应停止工作

以下是一个基本示例:

import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import javafx.application.Application;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.ProgressBarTableCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;

public class FileTaskExample extends Application {

    private static final Random RNG = new Random();

    private static final int MAX_THREADS = 4 ;

    private final Executor exec = Executors.newFixedThreadPool(MAX_THREADS, runnable -> {
        Thread t = new Thread(runnable);
        t.setDaemon(true);
        return t ;
    });

    @Override
    public void start(Stage primaryStage) {

        // table to display all tasks:
        TableView<FileProcessingTask> table = new TableView<>();

        TableColumn<FileProcessingTask, File> fileColumn = new TableColumn<>("File");
        fileColumn.setCellValueFactory(cellData -> new ReadOnlyObjectWrapper<File>(cellData.getValue().getFile()));
        fileColumn.setCellFactory(col -> new TableCell<FileProcessingTask, File>() {
            @Override
            public void updateItem(File file, boolean empty) {
                super.updateItem(file, empty);
                if (empty) {
                    setText(null);
                } else {
                    setText(file.getName());
                }
            }
        });
        fileColumn.setPrefWidth(200);

        TableColumn<FileProcessingTask, Worker.State> statusColumn = new TableColumn<>("Status");
        statusColumn.setCellValueFactory(cellData -> cellData.getValue().stateProperty());
        statusColumn.setPrefWidth(100);

        TableColumn<FileProcessingTask, Double> progressColumn = new TableColumn<>("Progress");
        progressColumn.setCellValueFactory(cellData -> cellData.getValue().progressProperty().asObject());
        progressColumn.setCellFactory(ProgressBarTableCell.forTableColumn());
        progressColumn.setPrefWidth(100);

        TableColumn<FileProcessingTask, Long> resultColumn = new TableColumn<>("Result");
        resultColumn.setCellValueFactory(cellData -> cellData.getValue().valueProperty());
        resultColumn.setPrefWidth(100);

        TableColumn<FileProcessingTask, FileProcessingTask> cancelColumn = new TableColumn<>("Cancel");
        cancelColumn.setCellValueFactory(cellData -> new ReadOnlyObjectWrapper<FileProcessingTask>(cellData.getValue()));
        cancelColumn.setCellFactory(col -> {
            TableCell<FileProcessingTask, FileProcessingTask> cell = new TableCell<>();
            Button cancelButton = new Button("Cancel");
            cancelButton.setOnAction(e -> cell.getItem().cancel());

            // listener for disabling button if task is not running:
            ChangeListener<Boolean> disableListener = (obs, wasRunning, isNowRunning) -> 
                cancelButton.setDisable(! isNowRunning);

            cell.itemProperty().addListener((obs, oldTask, newTask) -> {
                if (oldTask != null) {
                    oldTask.runningProperty().removeListener(disableListener);
                }
                if (newTask == null) {
                    cell.setGraphic(null);
                } else {
                    cell.setGraphic(cancelButton);
                    cancelButton.setDisable(! newTask.isRunning());
                    newTask.runningProperty().addListener(disableListener);
                }
            });

            return cell ;
        });
        cancelColumn.setPrefWidth(100);

        table.getColumns().addAll(Arrays.asList(fileColumn, statusColumn, progressColumn, resultColumn, cancelColumn));

        Button cancelAllButton = new Button("Cancel All");
        cancelAllButton.setOnAction(e -> 
            table.getItems().stream().filter(Task::isRunning).forEach(Task::cancel));

        Button newTasksButton = new Button("Process files");
        FileChooser chooser = new FileChooser();
        newTasksButton.setOnAction(e -> {
            List<File> files = chooser.showOpenMultipleDialog(primaryStage);
            if (files != null) {
                files.stream().map(FileProcessingTask::new).peek(exec::execute).forEach(table.getItems()::add);
            }
        });

        HBox controls = new HBox(5, newTasksButton, cancelAllButton);
        controls.setAlignment(Pos.CENTER);
        controls.setPadding(new Insets(10));

        BorderPane root = new BorderPane(table, null, null, controls, null);

        Scene scene = new Scene(root, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static class FileProcessingTask extends Task<Long> {

        private final File file ;

        public FileProcessingTask(File file) {
            this.file = file ;
        }

        public File getFile() {
            return file ;
        }

        @Override
        public Long call() throws Exception {

            // just to show you can return the result of the computation:
            long fileLength = file.length();

            // dummy processing, in real life read file and do something with it:
            int delay = RNG.nextInt(50) + 50 ;
            for (int i = 0 ; i < 100; i++) {
                Thread.sleep(delay);
                updateProgress(i, 100);

                // check for cancellation and bail if cancelled:
                if (isCancelled()) {
                    updateProgress(0, 100);
                    break ;
                }
            }

            return fileLength ;
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
导入java.io.File;
导入java.util.array;
导入java.util.List;
导入java.util.Random;
导入java.util.concurrent.Executor;
导入java.util.concurrent.Executors;
导入javafx.application.application;
导入javafx.beans.property.ReadOnlyObjectWrapper;
导入javafx.beans.value.ChangeListener;
导入javafx.concurrent.Task;
导入javafx.concurrent.Worker;
导入javafx.geometry.Insets;
导入javafx.geometry.Pos;
导入javafx.scene.scene;
导入javafx.scene.control.Button;
导入javafx.scene.control.TableCell;
导入javafx.scene.control.TableColumn;
导入javafx.scene.control.TableView;
导入javafx.scene.control.cell.ProgressBarTableCell;
导入javafx.scene.layout.BorderPane;
导入javafx.scene.layout.HBox;
导入javafx.stage.FileChooser;
导入javafx.stage.stage;
公共类FileTaskExample扩展了应用程序{
私有静态最终随机RNG=新随机();
私有静态最终int MAX_线程=4;
private final Executor exec=Executors.newFixedThreadPool(最大线程数,可运行->{
螺纹t=新螺纹(可运行);
t、 setDaemon(true);
返回t;
});
@凌驾
公共无效开始(阶段primaryStage){
//用于显示所有任务的表:
TableView table=新TableView();
TableColumn fileColumn=新的TableColumn(“文件”);
fileColumn.setCellValueFactory(cellData->new ReadOnlyObjectWrapper(cellData.getValue().getFile());
setCellFactory(col->newtableCell(){
@凌驾
公共v