首页 文章

JavaFX中的复杂并发:使用来自多个工作线程的ObservableLists和Properties

提问于
浏览
9

我有多个工作线程和一个JavaFX GUI,它报告这些线程中发生的事情 .

线程之间共享了大量数据,需要对其进行可视化 . 所以我使用ObservableList和Property来能够轻松地在JavaFX中显示数据 .

我做了一个小示例应用程序来显示类似于我的应用程序中发生的事情 . 它有2个列表,工作线程将数据从一个列表移动到另一个列表 . 状态字符串保持最新 . 完整的示例代码可以在http://codetidy.com/6569/找到(此代码将崩溃,请参阅后面的内容)

以下是共享的ObservableList和属性:

private ObservableList<String> newItems;
private ObservableList<String> readyItems;
private StringProperty status;

以下是它们在JavaFX中的使用方式:

listViewA.setItems(processor.getNewItems());
listViewB.setItems(processor.getReadyItems());
statusLabel.textProperty().bind(processor.getStatus());

工作线程更新这些列表和属性,但当然,它需要在JavaFX线程上执行此操作,这就是事情变得丑陋的地方 . 如果我不必在JavaFX线程上更新,这将是代码:

Runnable newItemAdder = new Runnable() {
      @Override
      public void run() {
          while(true) {
              synchronized (newItems) {
                  String newItem = checkForNewItem(); //slow
                  if (newItem != null) {
                      newItems.add(newItem);
                      newItems.notify();
                  }
                  if (newItems.size() >= 5)
                      status.set("Overload");
                  else
                      status.set("OK");
              }

              synchronized (readyItems) {
                  if (readyItems.size() > 10)
                      readyItems.remove(0);
              }

              try { Thread.sleep(200); } catch (InterruptedException e) { return; }
          }
      }
  };
  new Thread(newItemAdder).start();

  Runnable worker = new Runnable() {
      @Override
      public void run() {
          while(true) {
              List<String> toProcess = new ArrayList<String>();
              synchronized (newItems) {
                  if (newItems.isEmpty())
                      try { newItems.wait(); } catch (InterruptedException e) { return; }
                  toProcess.addAll(newItems);
              }

              for (String item : toProcess) {
                  String processedItem = processItem(item); //slow
                  synchronized (readyItems) {
                      readyItems.add(processedItem);
                  }
              }
          }
      }
  };
  new Thread(worker).start();

当然,使用Platform.runLater可以轻松解决一些问题:

Platform.runLater(new Runnable() {
     @Override
     public void run() {
         synchronized (newItems) {
             if (newItems.size() >= 5)
                 status.set("Overload");
             else
                 status.set("OK");
         }
     }
 });

这对于我只在任务中写入的属性/列表来说很好,只能在JavaFX GUI中读取 . 但是对于此示例中的列表执行此操作变得非常复杂,您需要对其进行同步,读取和写入 . 你需要添加很多Platform.runLater,你需要阻止,直到"runLater"任务完成 . 这会导致非常复杂且难以读写的代码(我设法以这种方式运行此示例,请参阅我的意思:http://codetidy.com/6570/) .

有没有其他方法让我的例子工作?我很感激任何其他解决方案或部分解决方案......

2 回答

  • 14

    背景资料

    Task javadoc包括许多用于在JavaFX中的线程之间传递数据的并发使用模式 .

    Task包括便利数据传输方法,例如updateMessage,可以用来代替具有用户定义状态属性的Runnable .

    在适当的时候,请考虑使用为并发而设计的集合结构,例如BlockingQueue . 另一个优点是BlockingQueues可以有大小限制,这似乎是你想要的 .

    一些一般性建议

    • 在多个线程中使用可变的可观察项时要非常小心 . 很容易无意中触发导致竞争条件的更新,从应用程序线程更新活动场景图以及其他线程问题 .

    • 尽可能使用immutable data而不是可变的可观察项目 .

    • 利用JavaFX concurrencyjava.util.concurrent库中的一些更高级别的实用程序 .

    • 尽可能避免显式同步和通知语句 .

    • 小心将同步或其他可能的阻塞语句放在JavaFX应用程序线程上运行的代码中 - 因为您可能会使GUI无响应 .

    • 仅在需要与JavaFX线程交互时才使用JavaFX并发实用程序 .

    • 使用标准Java并发实用程序从JavaFX线程执行大量非常复杂的多线程处理 . 有一个协调JavaFX任务来合并和控制UI反馈 .

    以上只是经验法则,不需要在教学上遵循 .

    合理复杂的线程样本

    • A chart renderer演示了上述一些原则,用于呈现300个图表,同时仍保持UI响应进度更新和用户交互 .
  • 1

    完整示例的原始链接和jewelsea的示例解决方案已经死了,所以我将添加一个答案,简要总结我最终做的事情 .

    为了方便起见,让's assume you start with 1 class that holds your data model (let'将其称为 DataModel ) . 更改它的多个线程使用此类的实例 .

    现在的问题是你想在javaFX中使用数据模型,但你不能简单地改变你的数据模型使用 PropertyObservableList 等 . 如果这样做,将从非javafx线程调用监听器,并且GUI绑定到他们会抛出异常 .

    相反,您需要为javaFX创建一个单独的数据模型类 . 这只是原始版本的JavaFX版本(我们称之为 FXDataModel ) . 所以这个版本包含相同的信息,但它使用javaFX PropertyObservableList . 这样,您就可以将GUI绑定到它 .

    下一步是使用 DataModel 实例定期更新 FXDataModel 实例 . 要执行此操作,请将 update(DataModel dataModel) 方法添加到 FXDataModel ,该方法将复制数据原始数据模型进入 FXDataModel 实例 . 必须始终在javaFX线程上调用此更新函数 . 最后,您需要做的就是定期调用该更新函数 .

    在我的实际场景中,我每200毫秒调用一次更新函数,这足以能够在GUI中显示数据模型的实时视图 . (如果您想要的不仅仅是数据模型的视图,并且您想要从GUI更改内容,那么事情会变得更复杂,但这不是我需要做的事情)

相关问题