Propper JavaFX线程实现

时间:2016-02-17 20:49:45

标签: multithreading javafx concurrency task

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

这是JavaFX类中的重要部分

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线程,这基本上会破坏任务的目的。我希望它能够实时显示进度,只需显示当时添加的曲目数量。

2 个答案:

答案 0 :(得分:0)

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

  1. 对UI 的任何更改必须在FX应用程序主题上进行。不执行此操作会导致IllegalStateException在运行时抛出,或者可能使UI处于不一致状态,从而可能在将来的任意点导致不可预测的行为。
  2. 任何需要很长时间才能运行的代码应在后台线程上执行。如果不这样做将导致UI在代码运行时无响应。
  3. 此外,有关于线程的一般规则:

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

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

    特别是,Task类会公开各种属性(包括stateprogressmessagevalue)以及线程安全{ {1}}方法。更新方法可以安全地从任何线程调用,并且将确保在UI线程上更新属性,并限制对它们的更新次数(如果在更新UI时发生更新,则基本上将更新合并在一起)。这意味着从后台线程调用updateXXX方法是安全的,并观察UI线程中的属性。此外,您可以根据需要随时调用这些方法,而不需要&#34; flooding&#34; UI线程并导致它变得没有响应。

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

    您的任务子类可以:

    1. 使用message属性更新已处理的曲目数
    2. 返回生成的曲目列表
    3. 从UI代码中,您可以将标签的文本绑定到message属性。您还可以使用setOnFailed处理程序在任务完成时更新UI。

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

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

      static

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

      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;
          }
      }
      

答案 1 :(得分:-2)

您可以向ObservableArrayList添加侦听器。

         pjesme.addListener(new ListChangeListener<Track>() {
            @Override
        public void onChanged(ListChangeListener.Change<? extends Track> c) {
            /* Do your Stuff in the gui. Like having the status bar or things */
        }
         });

添加侦听器后,只需将其交给构造函数

即可
       public PathGetter(String path, ObservableList<Track> pjesme) {
          fullPath = path;
          this.pjesme = pjesme;
       }

并更改PathGetter类中的ObersvableList。因此,每次向List添加内容时,都会调用eventlistener,您将有机会更新GUI中的内容。