2015-04-19 106 views
2

我有一个选项可供用户从FileChooser提交多个文件以供某些代码处理。结果将是读取文件的IO,然后是存储数据的实际繁重计算。允许用户选择多个文件,并且由于文件处理不依赖于任何其他选定的文件,这使得我的生活更容易通过线程处理。正确使用JavaFX任务执行多线程和线程池

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

最后一个要求是我不让用户通过打开大量文件来扼杀系统。因此,我认为线程池中的线程数量有限(让我们假设我将其限制为4以获得任意数量)。

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

我检查了this resource已经,所以如果答案在那里,那么我误解了文章。

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

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

  • 我是不是在一个包含最大线程数的地方做单身我愿意让用户?

我宁愿已经使用在一个Java库,因为我不是一个多线程的专家,并很担心,我可能这样做不对。由于线程错误似乎是地球上调试的最邪恶的东西,我试图很难很难确保我尽可能正确地做到这一点。

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

编辑:我应该注意到,我通常是新的线程,我已经使用过它们,我正在阅读关于它们的书籍,但这将是我第一次使用它们,我真的很想这样做正常。

+1

看看这个[样品](https://gist.github.com/jewelsea/4947946),其使用['ExecutorService'](HTTPS: //docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html)从['Executors']获取(https://docs.oracle.com/javase/8/docs/ api/java/util/concurrent/Executors.html)来管理多个任务,并在以下答案中进一步讨论:[如何在JavaFX2中的任务之间重置进度指示器?](http://stackoverflow.com/questions/16368793/如何对复位进度指示器之间的任务 - 在-javafx2) – jewelsea

回答

6

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,有几件事情要牢记:

不得更新从后台线程的用户界面。由于您的Task已提交给上述执行程序,因此将在后台线程上调用call()方法。如果在执行call方法期间确实需要更改UI,则可以将代码更改为Platform.runLater(...)中的UI,但最好进行结构组合,以避免出现这种情况。特别是,Task有一组updateXXX(...)方法,用于更改FX应用程序线程上相应属性的值。您的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<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); 
    } 
}