Propper JavaFX线程实现 [英] Propper JavaFX thread implementation

查看:183
本文介绍了Propper JavaFX线程实现的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的GUI中,我有一个TableView,它应该显示一个已加载文件的列表,在一个名为PathGetter的类完成后将它们加载到ObservableArrayList中,但我无法正确实现该任务。

In my GUI I have a TableView that should show a list of loaded files, after a class called PathGetter has finished loading them into an ObservableArrayList, but I just can't implement the task correctly.

这是JavaFX类中的重要部分

This is the important part in the JavaFX class

browseButton.setOnAction(event -> {
            File dir = folderPicker.showDialog(bwindow);
            if(dir != null){
                directoryLocation.setText(String.valueOf(dir));
                bottom.getChildren().add(new javafx.scene.control.Label("Loading Tracks"));
                //PathGetter.getPath(directoryLocation.getText());
                PathGetter task = new PathGetter(directoryLocation.getText());
                Thread th = new Thread(task);
                try {
                    pjesme = FXCollections.observableArrayList();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                selection.setItems(pjesme);
                chSelAll.setDisable(false);
                chSelIncomplete.setDisable(false);
                chSelNoCover.setDisable(false);
            }

        });

这是应该工作的类

public class PathGetter extends Task<ObservableList<Track>> {

    static boolean getSubDirs;
    static ArrayList <Track> allFiles;
    public static int trNr = 0;
    private static String fullPath;

    public PathGetter(String path) {
        fullPath = path;
    }

    public static int getTrNr() {
        return trNr;
    }

    public static void setTrNr(int trNr) {
        PathGetter.trNr = trNr;
    }

    public static boolean isSupported (File f){
        //supported file types
        if(String.valueOf(f).endsWith(".flac") || String.valueOf(f).endsWith(".mp3") || String.valueOf(f).endsWith(".aiff") || String.valueOf(f).endsWith(".ogg") || String.valueOf(f).endsWith(".mp4")){
            return true;
        }else{
            return false;
        }
    }

    @Override
    protected ObservableList<Track> call() throws Exception {
        getSubDirs = Browser.chSubDirs.isSelected();
        allFiles = new ArrayList<Track>();
        Queue<File> dirs = new LinkedList<File>();
        dirs.add(new File(fullPath));
        while (!dirs.isEmpty()) {
            for (File f : dirs.poll().listFiles()) {
                if (f.isDirectory() && getSubDirs == true) {
                    dirs.add(f);
                } else if (f.isFile() && isSupported(f)) {
                    allFiles.add(new Track(f));
                    setTrNr(getTrNr()+1);
                }
            }
        }
        ObservableList<Track> returnList = FXCollections.observableArrayList(allFiles);
        return returnList;
    }
}

我不明白如何让TableView等待对于要完成的任务,不会阻塞整个JavaFX线程,这基本上会破坏任务的目的。我希望它能够实时显示进度,只需显示当时添加的曲目数量。

I don't understand how to make the TableView wait for the task to be finished, without blocking the entire JavaFX thread, which basically defeats the purpose of a task. I want it to be able to show progress in real time, simply by displaying the number of added tracks at that moment.

推荐答案

JavaFX有两个特定的线程规则:

There are two threading rules specific to JavaFX:


  1. 对UI应用程序的任何更改必须在FX应用程序上进行线。不执行此操作会导致在运行时抛出 IllegalStateException ,或者可能使UI处于不一致状态,从而可能在将来的任意点导致不可预测的行为。

  2. 任何需要很长时间才能运行的代码应在后台线程上执行。如果不这样做将导致UI在代码运行时无响应。

  1. Any changes to the UI must be made on the FX Application Thread. Not doing this will either cause IllegalStateExceptions to be thrown at runtime, or may put the UI in an inconsistent state, potentially causing unpredicatable behavior at arbitrary points in the future.
  2. Any code that takes a long time to run should be performed on a background thread. Not doing so will cause the UI to become unresponsive while that code is running.

此外,还有一条关于线程的一般规则:

Additionally, there is a general rule about threading:

在多个线程中访问可变状态时必须小心。特别是,应该确保给定线程中的操作是原子的,并且可能需要特别小心以确保对一个线程中所做数据的状态的更改对另一个线程是可见的。

Care must be taken when accessing mutable state in multiple threads. In particular, operations in a given thread should be ensured to be atomic, and special care may need to be taken to ensure changes to the state of the data made in one thread are visible to another thread.

让最后一部分正确无误是特别具有挑战性的。此外,在UI环境中使用后台线程时,您几乎总是希望在后台线程和UI线程之间共享进程的结果,有时还要共享在此过程中计算的数据。因为这很有挑战性,所以JavaFX提供了一个 Task 类(和其他一些相关的类)来处理这个棘手的部分,以便涵盖大多数用例。

Getting this last part correct is particularly challenging. Additionally, when using a background thread in a UI environment, you almost always want to share the results of the process, and sometimes data that is computed during the process, between the background thread and the UI thread. Because this is challenging, JavaFX provides a Task class (and some other related classes) that take care of the trickier parts of this in order to cover most use cases.

特别是,任务类公开了各种属性(包括 state 进度消息),以及线程安全 updateXXX 方法。可以安全地从任何线程调用更新方法,并且将确保在UI线程上更新属性,并限制对它们的更新次数(如果在更新UI时发生更新,则基本上将更新合并在一起)。这意味着从后台线程调用 update 方法是安全的,并观察UI线程中的属性。此外,您可以随意调用这些方法,而不会泛滥UI线程并导致其无响应。

In particular, the Task class exposes various properties (including state, progress, message, and value), along with thread-safe updateXXX methods. The update methods are safe to be called from any thread, and will both ensure the properties are updated on the UI thread, and throttle the number of updates to them (coalescing updates together essentially if they occur within the time the UI is updated). This means it is safe to call the update methods from the background thread, and observe the properties in the UI thread. Additionally, you can call these methods as often as you like without "flooding" the UI thread and causing it to become unresponsive that way.

任务类还公开处理程序,以便从一种状态转换到另一种状态,例如 setOnSucceeded (当任务正常完成时调用)和 setOnFailed (当任务抛出异常时调用)。这些也在FX应用程序线程上处理。

The Task class also exposes handlers for transitioning from one state to another, such as setOnSucceeded (invoked when the task completes normally) and setOnFailed (invoked when the task throws an exception). These are also handled on the FX Application Thread.

您的任务子类可以:


  1. 使用message属性更新已处理的曲目数

  2. 返回生成的曲目列表

从UI代码中,您可以将标签的文本绑定到message属性。您还可以使用 onSucceeded 处理程序在任务完成时更新UI。

From the UI code, you can bind the text of a label to the message property. You can also use on onSucceeded handler to update the UI when the task completes.

确保不要在线程之间共享可变状态,除了由任务机制正确管理的线程之外,您应该正确封装您的类。这意味着不暴露任务本身操纵的任何状态。你的州都不应该是 static (并且没有明显的理由你不想这样做)。

To ensure you don't share mutable state between threads, other than that which is properly managed by the Task machinery, you should properly encapsulate your class. This means not exposing any state that is manipulated by the task itself. None of your state should be static (and there is no obvious reason you would want to do this anyway).

所以我会按如下方式编写任务:

So I would write the task as follows:

public class PathGetter extends Task<ObservableList<Track>> {

    private final boolean getSubDirs;
    private final String fullPath;

    public PathGetter(String path, boolean getSubDirs) {
        fullPath = path;
        this.getSubDirs = getSubDirs ;
    }

    public static boolean isSupported (File f){
        String fileName = f.toString();
        //supported file types
        return fileName.endsWith(".flac") 
               || fileName.endsWith(".mp3")  
               || fileName.endsWith(".aiff") 
               || fileName.endsWith(".ogg") 
               || fileName.endsWith(".mp4") ;
    }

    @Override
    protected ObservableList<Track> call() throws Exception {

        List<Track> allFiles = new ArrayList<Track>();
        Queue<File> dirs = new LinkedList<File>();
        dirs.add(new File(fullPath));
        while (!dirs.isEmpty()) {
            for (File f : dirs.poll().listFiles()) {
                if (f.isDirectory() && getSubDirs) {
                    dirs.add(f);
                } else if (f.isFile() && isSupported(f)) {
                    allFiles.add(new Track(f));
                    updateMessage("Number of tracks processed: "+allFiles.size());
                }
            }
        }
        ObservableList<Track> returnList = FXCollections.observableArrayList(allFiles);
        return returnList;
    }
}

现在,您可以通过UI执行以下操作:

Now from the UI you can do something like:

browseButton.setOnAction(event -> {
    File dir = folderPicker.showDialog(bwindow);
    if(dir != null){
        directoryLocation.setText(String.valueOf(dir));
        Label label = new Label("Loading Tracks");
        bottom.getChildren().add(label);

        PathGetter task = new PathGetter(directoryLocation.getText(), Browser.chSubDirs.isSelected());
        Thread th = new Thread(task);

        // keep label showing message from task:
        label.textProperty().bind(task.messageProperty());

        task.setOnSucceeded(e -> {
            selection.setItems(task.getValue());
            chSelAll.setDisable(false);
            chSelIncomplete.setDisable(false);
            chSelNoCover.setDisable(false);
        });

        task.setOnFailed(e -> {
            // handle exception ...

            // and log it
            task.getException().printStackTrace();
        });

        chSelAll.setDisable(true);
        chSelIncomplete.setDisable(true);
        chSelNoCover.setDisable(true);

        // make sure thread doesn't prevent application exit:
        th.setDaemon(true);

        // set it going:
        th.start();

    }

});

这篇关于Propper JavaFX线程实现的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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