正确使用JavaFX任务执行多线程和线程池 [英] Properly doing multithreading and thread pools with JavaFX Tasks

查看:2227
本文介绍了正确使用JavaFX任务执行多线程和线程池的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have an option for users to submit multiple files from a FileChooser to be processed by some code. The result will be IO for reading the file, then the actual heavy computation on the stored data. The user is allowed to select multiple files, and since file processing does not depend on any of the other files selected, that makes my life a lot easier to handle this with threads.

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

In addition, the user needs to have a list of buttons, one for each Task to cancel, and a "Cancel All" button. Therefore I have to consider the ability to selectively or collectively kill one or all of the Tasks.

最后一个要求是我不让用户扼杀系统开了一大堆文件。因此,我想一个线程数量有限的线程池(让我假装我会将它限制在4为任意数字)。

The last requirement is that I don't let the user choke the system by opening a ton of files. Therefore I figure a thread pool with a limited number of threads (let's pretend I'll cap it at 4 for some arbitrary number).

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

I am unsure how to properly go about setting this all up. I have the logic of what I need to do but using the right classes is where I am stuck.

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

I've checked this resource already, so if the answer is somehow in there then I've misread the article.


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

  • Are there any JavaFX classes that can help me with this situation?

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

If not, how would I mix a Task with some kind of thread pool? Do I have to make my own thread pool or is there one that is already provided for me?

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

Am I to make a singleton somewhere that contains the max number of threads I am willing to allow the user?

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

I would prefer to use one already in Java library since I am not a multithreading expert, and am worried that I could potentially do it wrong. Since thread bugs appear to be the most evil thing on the planet to debug, I'm trying very hard to make sure I do this as correctly as possible.

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

If there are no ways to do this and I have to roll my own implementation, what is the best way to go about doing this?

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

I should note that I am generally new to threads, I have used them before and I'm reading books on them, but this will be my first major use of them and I'd really like to do it properly.

推荐答案

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

JavaFX has a javafx.concurrent API; in particular, the Task class fits your use case very nicely. This API is designed to work in conjunction with the java.util.concurrent API. For example, Task is an implementation of FutureTask, so it can be submitted to an Executor. As you want to use a thread pool, you can create an Executor that implements a thread pool for you, and submit your tasks to it:

final int MAX_THREADS = 4 ;

Executor exec = Executors.newFixedThreadPool(MAX_THREADS);

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

As these threads are running in the background of a UI application, you probably don't want them to prevent application exit. You can achieve this by making the threads created by your executor daemon threads:

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

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

The resulting executor will have a pool of up to MAX_THREADS threads. If tasks are submitted when no threads are available, they will wait in a queue until a thread becomes available.

实现实际的任务,有几点需要注意:

To implement the actual Task, there are a few things to bear in mind:

必须从后台线程更新UI。由于您的任务已提交给上面的执行程序,因此将在后台线程上调用 call()方法。如果您确实需要在执行调用方法期间更改UI,则可以在 Platform.runLater中包装更改UI的代码(。 ..),但结构化的东西最好避免这种情况。特别是,任务有一组 updateXXX(...)方法,用于更改相应<$的值FX应用程序线程上的c $ c>任务属性。您的UI元素可以根据需要绑定到这些属性。

You must not update the UI from a background thread. Since your Task is submitted to the executor above, it's call() method will be invoked on a background thread. If you really need to change the UI during the execution of the call method, you can wrap the code that changes the UI in Platform.runLater(...), but it is better to structure things so that you avoid this situation. In particular, the Task has a set of updateXXX(...) methods that change the values of corresponding Task properties on the FX Application thread. Your UI elements can bind to these properties as needed.

建议调用方法不要访问任何属性共享数据(除了通过上面提到的 updateXXX(...)方法)。实例化任务子类设置仅最终变量,具有 call()方法计算一个值,然后返回该值。

It is advisable for the call method not to access any shared data (other than via the updateXXX(...) methods mentioned above). Instantiate your Task subclass setting only final variables, have the call() method compute a value, and return the value.

要取消任务任务类定义内置的 cancel()方法。如果你有一个长期运行的 call()方法,你应该定期检查 isCancelled()的值并停止如果它返回工作 true

For canceling the Task, the Task class defines a built-in cancel() method. If you have a long-running call() method, you should periodically check the value of isCancelled() and stop doing work if it returns 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);
    }
}

这篇关于正确使用JavaFX任务执行多线程和线程池的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆