草庐IT

java - JavaFX 中的复杂并发 : using ObservableLists and Properties from multiple worker threads

coder 2024-03-06 原文

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

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

最佳答案

背景信息

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

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

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

一些一般性建议

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

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

相当复杂的线程示例

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

关于java - JavaFX 中的复杂并发 : using ObservableLists and Properties from multiple worker threads,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18530493/

有关java - JavaFX 中的复杂并发 : using ObservableLists and Properties from multiple worker threads的更多相关文章

随机推荐