2015-04-19 187 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); 
    } 
}