7

私のコードはTreeItem<String> バックグラウンド タスクで作成します。これは、多数のコードがあり、それらの作成にかなりの時間がかかり、アプリケーションがフリーズするためです。この例ではあまり意味がありませんが、実際のアプリケーションで遭遇した問題を示しています。ノードを展開すると、プログラムは ConcurrentModificationException をスローします。

jdk1.7.0_17 と JavaFX 2.2.7 を使用しています

Treeスレッドセーフを作成する方法や問題を回避する方法を知っている人はいますか?

例外

java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:819)
    at java.util.ArrayList$Itr.next(ArrayList.java:791)
    at com.sun.javafx.collections.ObservableListWrapper$ObservableListIterator.next(ObservableListWrapper.java:681)
    at javafx.scene.control.TreeItem.updateExpandedDescendentCount(TreeItem.java:788)
    ...

コード

import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

import java.security.SecureRandom;
import java.util.Random;


public class ConcurrentExample extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        TreeView<String> treeView = new TreeView<String>(createNode("root"));
        HBox hBox = new HBox();
        hBox.getChildren().addAll(treeView);
        Scene scene = new Scene(hBox);
        stage.setScene(scene);
        stage.show();
    }

    Random r = new SecureRandom();

    public TreeItem<String> createNode(final String b) {
        return new TreeItem<String>(b) {
            private boolean isLeaf;
            private boolean isFirstTimeChildren = true;
            private boolean isFirstTimeLeaf = true;

            @Override
            public ObservableList<TreeItem<String>> getChildren() {
                if (isFirstTimeChildren) {
                    isFirstTimeChildren = false;
                    buildChildren(super.getChildren());
                }
                return super.getChildren();
            }

            @Override
            public boolean isLeaf() {
                if (isFirstTimeLeaf) {
                    isFirstTimeLeaf = false;
                    isLeaf = r.nextBoolean() && r.nextBoolean() && r.nextBoolean();
                }
                return isLeaf;
            }

            private void buildChildren(final ObservableList<TreeItem<String>> children) {
                if (!this.isLeaf()) {
                    Task<Integer> task = new Task<Integer>() {
                        @Override
                        protected Integer call() throws Exception {
                            int i;
                            int max = r.nextInt(500);
                            for (i = 0; i <= max; i++) {
                                children.addAll(new TreeItem[]{createNode("#" + r.nextInt())});
                            }
                            return i;
                        }
                    };
                    new Thread(task).start();
                }
            }
        };
    }

}
4

3 に答える 3

4

JavaFX アプリケーション スレッド以外のスレッドから、シーン グラフに関連する (および TreeView の項目を含む) アクティブなノードとデータに影響を与えるものを直接変更することはできません。

問題の解決に役立つサンプル タスク (ObservableList または Partial Results を返すタスク)については、タスクのドキュメントを参照してください。新しい ObservableList で Task に新しい TreeItems を作成し、Task が終了したら (そして JavaFX アプリケーションスレッドで)、ツリーのアイテムリストを Task から返された ObservableList に設定する必要があります。

http://docs.oracle.com/javafx/2/api/javafx/concurrent/Task.html

以下は、これらの原則のいくつかに従い、ConcurrentModificationExceptions を持たないコードの更新バージョンです。

TreeItem が updateExpandedDecendentCount() を呼び出すときに、addAll(List) 呼び出しを正確に実行してはならないのはなぜですか?

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

import java.security.SecureRandom;
import java.util.Random;


public class ConcurrentExample extends Application {
  public static void main(String[] args) {
    launch(args);
  }

  @Override
  public void start(Stage stage) throws Exception {
    TreeView<String> treeView = new TreeView<>(createNode("root"));
    HBox hBox = new HBox();
    hBox.getChildren().addAll(treeView);
    Scene scene = new Scene(hBox);
    stage.setScene(scene);
    stage.show();
  }

  Random r = new SecureRandom();

  public TreeItem<String> createNode(final String b) {
    return new TreeItem<String>(b) {
      private boolean isLeaf;
      private boolean isFirstTimeChildren = true;
      private boolean isFirstTimeLeaf = true;

      @Override
      public ObservableList<TreeItem<String>> getChildren() {
        if (isFirstTimeChildren) {
          isFirstTimeChildren = false;
          buildChildren(super.getChildren());
        }
        return super.getChildren();
      }

      @Override
      public boolean isLeaf() {
        if (isFirstTimeLeaf) {
          isFirstTimeLeaf = false;
          isLeaf = r.nextBoolean() && r.nextBoolean() && r.nextBoolean();
        }
        return isLeaf;
      }

      private void buildChildren(final ObservableList<TreeItem<String>> children) {
        final ObservableList<TreeItem<String>> taskChildren = FXCollections.observableArrayList();

        if (!this.isLeaf()) {
          Task<Integer> task = new Task<Integer>() {
            @Override
            protected Integer call() throws Exception {
              int i;
              int max = r.nextInt(500);
              for (i = 0; i <= max; i++) {
                taskChildren.addAll(new TreeItem[]{createNode("#" + r.nextInt())});
              }
              return i;
            }
          };

          task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
            @Override public void handle(WorkerStateEvent workerStateEvent) {
              children.setAll(taskChildren);
            }
          });
          new Thread(task).start();
        }
      }
    };
  }

}

更新 - ソリューションが機能する理由の説明

上記のソリューションは、関連する が同時に変更されることはないConcurrentModificationExceptionため、を受け取ることができませんObservableLists

  • コレクションは、タスクのtaskChildrenユーザー スレッドでのみ変更され、かつ
  • シーングラフにアクティブにアタッチされたツリー アイテムの子は、タスクの JavaFX アプリケーション スレッドでのみ変更されます。

これは、次の項目によって保証されます。

  1. taskChildren.addAllタスクのcallメソッドで呼び出されます。
  2. タスクの call メソッドは、ユーザー スレッドで呼び出されます。
  3. children.setAll(taskChildren)JavaFX アプリケーションスレッドで呼び出されます。
  4. JavaFX システムはonSucceeded、タスクのイベント ハンドラーが JavaFX アプリケーション スレッドで呼び出されるようにします。
  5. taskChildrenタスクの完了後、特定のリスト に子が追加されることはなく、リストが変更されることもありません。
  6. 実行されるタスクごとに新しいtaskChildrenリストが作成されるため、特定のtaskChildrenリストがタスク間で共有されることはありません。
  7. ツリーに変更が加えられるたびに、新しいタスクが作成されます。
  8. タスクのセマンティクスは、特定のタスクを 1 回しか実行できず、再起動できないようなものです。
  9. アクティブなシーングラフにアタッチされた TreeItem の子は、タスクが正常に完了して処理が停止した後に、JavaFX アプリケーション スレッドでのみ変更されます。

呼び出しaddAll(List)が正確に実行されるべきではないのはなぜですか?TreeItemupdateExpandedDescendentCount()

updateExpandedDescendentCount()はパブリックTreeItemAPI の一部ではありません。これは の内部実装メソッドであり、TreeViewこの問題の解決には関係ありません。


部分更新の更新

JavaFX タスクのドキュメントには、「部分的な結果を返すタスク」の解決策があります。同様のものを使用すると、「「buildChildren」スレッドが終了してノードを表示するのを待たなければならないため、最初はアプリケーションが使用できない」という問題を解決できるはずです。これは、部分的な結果のソリューションでは、結果をビルダー タスク スレッドから FX アプリケーション スレッドに小さなバッチで "ストリーミング" できるためです。

この種のソリューションは、上で提供したソリューションよりも実装が複雑ですが、要件に合ったレスポンシブ UI を実現できるはずです。いつものように、同時発生の状況に対処するときは、元の投稿で経験したように、共有データが同時に変更されて潜在的な競合状態が発生しないように、特別な注意を払う必要があります。

于 2013-04-22T02:40:05.280 に答える
-1

単純明快です。クライアント コードがコレクションの反復を開始すると、コレクションを更新し続けます。

スレッドを削除するか、イテレータが作成される前にスレッドが終了していることを確認するか、何らかの同期を行ってください。

于 2013-04-20T21:26:45.767 に答える