11

我有多个工作线程和一个 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/ )。

还有其他方法可以使我的示例正常工作吗?我会很感激任何其他解决方案或部分解决方案......

4

2 回答 2

17

背景信息

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

Task包括方便的数据传输方法,例如updateMessage并且可以使用用户定义的 status 属性代替 Runnable。

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

一些一般性建议

  1. 在多个线程中使用可变的可观察项时要非常小心。很容易无意中触发导致竞态条件的更新、对应用程序线程之外的活动场景图的更新以及其他线程问题。
  2. 尽可能使用不可变数据而不是可变的可观察项。
  3. 利用JavaFX 并发库和java.util.concurrent库中的一些高级实用程序。
  4. 尽可能避免显式同步和通知语句。
  5. 小心在 JavaFX 应用程序线程上运行的代码中放置同步或其他可能阻塞的语句 - 因为您可能会使您的 GUI 无响应。
  6. 仅当您需要与 JavaFX 线程交互时才使用 JavaFX 并发实用程序。
  7. 使用标准 Java 并发实用程序在 JavaFX 线程之外进行大量非常复杂的多线程处理。有一个用于合并和控制 UI 反馈的协调 JavaFX 任务。

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

合理复杂的线程样本

  • 一个图表渲染器,它演示了上面的一些原理来渲染 300 个图表,同时仍然保持 UI 响应进度更新和用户交互。
于 2013-08-30T16:16:47.280 回答
1

完整示例的原始链接和jewelsea 的示例解决方案已失效,因此我将添加一个答案,并简要总结我最终所做的事情。

为了简单起见,我们假设您从 1 个保存数据模型的类开始(我们称之为DataModel)。这个类的一个实例被多个改变它的线程使用。

现在的问题是您想在 javaFX 中使用数据模型,但是您不能简单地更改要使用的数据模型Property等等ObservableList。如果这样做,将从非 javafx 线程调用侦听器,并且绑定到它们的 GUI 将抛出异常。

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

下一步是FXDataModel使用实例定期更新DataModel实例。为此,您向 中添加一个update(DataModel dataModel)方法FXDataModel,该方法将数据从原始数据模型复制到FXDataModel实例中。此更新函数必须始终在 javaFX 线程上调用。最后,您需要做的就是定期调用该更新函数。

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

于 2014-08-22T08:34:38.890 回答