【问题标题】:Updating JavaFX ProgressIndicator multiple times from a Thread从一个线程多次更新 JavaFX ProgressIndicator
【发布时间】:2017-10-07 03:14:48
【问题描述】:

我正在使用 Javafx 设计一个多线程应用程序,并希望有一个 TableView,其中包含每个线程的名称和进度列。经过大量研究后,我在这里找到了一个类似的示例:

JavaFX Update progressbar in tableview from Task

(指向这个:'https://community.oracle.com/message/10999916')

然而,我遇到的问题在这个例子中得到了很好的说明;如何多次调用“任务”对象来更新 ProgressIndicator?

我对 Oracle 文档的理解是,Task 对象“是一次性类,不能重用”。这样看来,一个任务对象的 call() 方法只能调用一次。我需要在任务通过 Thread 类的过程中多次更新它,而不是调用一次并通过 For 循环任意递增。

我已阅读有关绑定到侦听器和创建服务类的信息,但我不确定这些是否是此问题的实际解决方案。因此,我想问一下这在 Javafx 中是否可行,或者我是否忽略了某些东西。如果过去有人已经做到了这一点,如果您能够通过前面提供的示例说明如何做到这一点,那将非常有帮助。

对此的任何指导将不胜感激,谢谢。

-画了

编辑 1:我编辑了我的措辞,因为它不准确。

编辑 2:这是一个带有一些伪代码的示例。假设我有一堂课,代码如下:

public static class TaskEx extends Task<Void>{

  @Override
  protected Void call(){

    updateProgress(.5, 1);

  return null

}

public static void callThread() {
  TableView<TaskEx> table = new TableView<TaskEx>();
  //Some code for data in table.

  TableColumn progressColumn = new TableColumn ("Progress");
  progressColumn.setCellValueFactory(new PropertyValueFactor("progress");

  table.setItems(<data>);
  table.getColumns();addAll(progressColumn);

  ExecutorService executor = Executors.newFixedThreadPool(<SomeNumber>);

  for(TaskEx task : table.getItems(){
    Threading.ThreadClass newThread = new Threading.ThreadClass(task);
    executor.submit(newThread, <uniqueID>);
  }
}

然后说我有第二个线程类的逻辑:

static class ThreadClass extends Thread{
  Task progressTask;

  public ThreadClass(Task task, Integer id){
    progressTask = task;
  }

public void run(){
  ExecutorService executor =  Executors.newFixedThreadPool(<someNumber>);

  //This invokes the Task call for the correct progressIndicator in the Tableview.
  //It will correctly set the progressIndicator to 50% done.
  executor.submit(progressTask);

  /* Main logic of the Threading class that involves the 'id' passed in. */

  //This will do nothing because you cannot invoke the Task call more than once.
  executor.submit(progressTask);
  }
}

这是我需要的工作流程,但我不确定如何完成。

【问题讨论】:

  • “这样看来,一个任务对象只能调用一次'updateProgress'。” 这显然不是真的:您链接的示例调用updateProgress(...) in一个循环,因此每个任务都会多次调用它。所有文档意味着您只能调用一次Taskcall() 方法。您可以在call() 方法中多次调用updateProgress。您链接的示例似乎是您描述您想要做的事情的完整实现。你能解释一下你的问题有什么不同吗?
  • 嘿詹姆斯。我确定我措辞很糟糕。我的意思是,为了正确增加任务的进度,我需要多次调用任务的 call() 方法。我知道一旦我在 call() 方法中,我可以循环并继续调用 updateProgress,但我不知道当我调用它时线程需要多长时间才能完成。我曾计划在任务的 call() 方法位于线程逻辑中的某个点时调用它一次,然后在它进一步下降时再次调用它,依此类推。
  • 我想你一定误解了Task 的目的?任务代表“你正在做的事情”——所以它应该是你感兴趣的任务的进度。如果不是这样,您使用Task 做什么?你能发布一些代码来说明这个问题吗?
  • 换句话说,Task 是线程执行的Runnable - call() 方法本质上是run()Runnable 方法。 Task 是线程正在做的事情。
  • 在我看来,你可能做错了。据我所知,您可能产生了 X 个线程(不使用 Task)同时执行 X 个不同的事情。然后每个衍生线程创建其Task 并将进度更新回UI 线程(到单个ProgressBar)。如果这是您正在做的事情,那么您的 Task 是多余的 - 您不妨参考相应的 ProgressBar 并使用 Platform.runLater(() -&gt; progressBar.setProgress(x)); 来执行此操作。或者,您应该将它们转换为 Task,而不是 new Thread()

标签: java javafx javafx-8


【解决方案1】:

您似乎没有明白我们在说什么。您正在尝试在Thread.run() 中执行您的逻辑,然后每个线程都在创建一个Task 只是为了更新进度。

您真正需要的是将您的逻辑从Thread.run() 转移到Task.call()。你的线程实际上只是一个线程,它所做的只是运行一个Runnable 对象(即Task)。

public class TaskEx extends Task<Void> {
    @Override
    protected Void call() {
        // Do whatever you need this thread to do 
        updateProgress(0.5, 1);

        // Do the rest
        updateProgress(1, 1);
    }
}


public static void callThread() {
    TableView<TaskEx> table = new TableView<TaskEx>();
    ObservableList<TaskEx> data = FXCollections.observableArrayList<>();
    data.add(new TaskEx()); // Add the data you need

    TableColumn progressColumn = new TableColumn("Progress");
    progressColumn.setCellValueFactory(new PropertyValueFactory("progress"));
    progressColumn.setCellFactory(column -> {
        return new TableCell<TaskEx, Double> {
            private final ProgressBar bp = new ProgressBar();

            @Override
            public void updateItem(Double item, boolean empty) {
                super.updateItem(item, empty);

                if (empty || item == null) {
                    setText(null);
                    setGraphic(null);
                }
                else {
                    bp.setProgress(item.doubleValue());
                    setGraphic(bp);
                }
            }
        }
    });

    table.setItems(data);
    table.getColumns().add(progressColumn);

    ExecutorService executor = Executors.newFixedThreadPool(data.size());

    for (TaskEx task : table.getItems()) {
        executor.submit(task);
    }
}

这个实现删除了ThreadClass,因为不应该有任何逻辑必须在线程子类中完成。如果您确实需要在逻辑中访问线程对象,请从您的 TaskEx.call() 调用 Thread.getCurrentThread()

这个工具还打开了多个线程做同样的事情(这是毫无意义的)。如果你需要做一组不同的逻辑,你可以创建一组不同的Task子类,或者在TaskEx中添加一个构造函数,接收Runnable对象。

例如

public class TaskEx extends Task<Void> {
    private final Runnable[] logics;

    public TaskEx(Runnable[] logics) {
        this.logics = logics;
    }

    @Override
    protected Void call() {
        for (int i = 0; i < logics.length; i++) {
            logics[i].run();
            updateProgress(i, logics.length);
        }
    }
}

【讨论】:

  • 谢谢杰!你和 James_D 帮助我更好地理解“任务”及其目的,尽管我承认还有很长的路要走 :)
  • @DrewB 学习是一个持续的过程。我从 James_D、jewelsea 和 DVarga 等专家那里得到了很多(直接和间接的)指导 :)
猜你喜欢
  • 2018-04-06
  • 1970-01-01
  • 1970-01-01
  • 2018-05-24
  • 2015-10-26
  • 2019-01-26
  • 2013-04-08
  • 2015-05-28
  • 2014-08-26
相关资源
最近更新 更多