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

我有一个选项,用户可以从FileChooser提交多个文件,以便由某些代码处理。 结果将是用于读取文件的IO,然后是对存储数据的实际繁重计算。 允许用户选择多个文件,并且由于文件处理不依赖于所选择的任何其他文件,因此使用线程更容易处理此问题。

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

最后一个要求是我不会让用户通过打开大量文件来阻塞系统。 因此,我想到了一个线程池数量有限的线程池(让我们假设我将它限制为4,任意数字)。

我不确定如何正确地设置这一切。 我有我需要做的逻辑,但使用正确的类是我被困住的地方。

我已经检查了这个资源 ,所以如果答案是某种方式,那么我就误读了这篇文章。

  • 是否有任何JavaFX类可以帮助我解决这种情况?

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

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

我更喜欢使用已经在Java库中的一个,因为我不是multithreading专家,我担心我可能做错了。 由于线程错误似乎是地球上调试的最邪恶的东西,我正在努力确保尽可能正确地做到这一点。

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

编辑:我应该注意,我通常是线程的新手,我之前使用过它们并且我正在阅读它们,但这将是我第一次使用它们,我真的很想做到这一点。

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

 final int MAX_THREADS = 4 ; Executor exec = Executors.newFixedThreadPool(MAX_THREADS); 

由于这些线程在UI应用程序的后台运行,您可能不希望它们阻止应用程序退出。 您可以通过执行执行程序守护程序线程创建的线程来实现此目的:

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

生成的执行程序将具有最多MAX_THREADS线程的池。 如果在没有线程可用时提交任务,它们将在队列中等待,直到线程可用。

要实现实际Task ,需要记住以下几点:

不能从后台线程更新UI。 由于您的Task被提交给上面的执行程序,它的call()方法将在后台线程上调用。 如果您确实需要在执行call方法期间更改UI,则可以在Platform.runLater(...)包装更改UI的代码,但最好是对事物进行结构化以避免这种情况。 特别是, Task有一组updateXXX(...)方法,用于更改FX Application线程上相应Task属性的值。 您的UI元素可以根据需要绑定到这些属性。

建议call方法不要访问任何共享数据(除了通过上面提到的updateXXX(...)方法)。 实例化您的Task子类设置只有final变量,让call()方法计算一个值,并返回该值。

为了取消TaskTask类定义了一个内置的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 table = new TableView<>(); TableColumn fileColumn = new TableColumn<>("File"); fileColumn.setCellValueFactory(cellData -> new ReadOnlyObjectWrapper(cellData.getValue().getFile())); fileColumn.setCellFactory(col -> new TableCell() { @Override public void updateItem(File file, boolean empty) { super.updateItem(file, empty); if (empty) { setText(null); } else { setText(file.getName()); } } }); fileColumn.setPrefWidth(200); TableColumn statusColumn = new TableColumn<>("Status"); statusColumn.setCellValueFactory(cellData -> cellData.getValue().stateProperty()); statusColumn.setPrefWidth(100); TableColumn progressColumn = new TableColumn<>("Progress"); progressColumn.setCellValueFactory(cellData -> cellData.getValue().progressProperty().asObject()); progressColumn.setCellFactory(ProgressBarTableCell.forTableColumn()); progressColumn.setPrefWidth(100); TableColumn resultColumn = new TableColumn<>("Result"); resultColumn.setCellValueFactory(cellData -> cellData.getValue().valueProperty()); resultColumn.setPrefWidth(100); TableColumn cancelColumn = new TableColumn<>("Cancel"); cancelColumn.setCellValueFactory(cellData -> new ReadOnlyObjectWrapper(cellData.getValue())); cancelColumn.setCellFactory(col -> { TableCell cell = new TableCell<>(); Button cancelButton = new Button("Cancel"); cancelButton.setOnAction(e -> cell.getItem().cancel()); // listener for disabling button if task is not running: ChangeListener 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 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 { 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); } }