2016-04-13 30 views
2

JFX TableView(概念証明アプリケーションの証明)でセルを頻繁に更新しようとしました。 FXMLを介してTableViewをロードし、ExecutorServiceを起動してセルの値を変更します。JavaFX TableViewの頻繁な更新

私がアプリケーションを起動すると、最初の300万から400万の要素に対してアップデートが機能していることがわかりました。アップデートを遅くすると(MAGIC#1参照)動作します(10msはまだ速いですが、100msの遅延が働きます)。だから私はそれがスレッドの問題かもしれないと思った。

しかし、空のChangeListener(MAGIC#2参照)をプロパティに追加すると、それが正常に動作することがわかりました。マジック#1の必要がなくても。

何か間違っていますか?私は別の方法で細胞を更新する必要がありますか?

ご協力いただきありがとうございます。

テーブルビュー内の要素:

public class Element { 
    public static final AtomicInteger x = new AtomicInteger(0); 
    private final StringProperty nameProperty = new SimpleStringProperty("INIT"); 

    public Element() { 
    // MAGIC#2 
    // this.nameProperty.addListener((observable, oldValue, newValue) -> {}); 
    } 

    public void tick() { 
    this.setName(String.valueOf(x.incrementAndGet())); 
    } 

    public String getName() ... 
    public void setName(String name)... 
    public StringProperty nameProperty() ... 
} 

FXML用コントローラ:

public class TablePerformanceController implements Initializable { 
    private final ObservableList<Element> data = FXCollections.observableArrayList(); 

    public Runnable changeValues =() -> { 
    while (true) { 
     if (Thread.currentThread().isInterrupted()) break; 
     data.get(0).tick(); 
     // MAGIC#1 
     // try { Thread.sleep(100); } catch (Exception e) {} 
    } 
    }; 

    private ExecutorService executor = null; 

    @FXML 
    public TableView<Element> table; 

    @Override 
    public void initialize(URL location, ResourceBundle resources) { 
    this.table.setEditable(true); 

    TableColumn<Element, String> nameCol = new TableColumn<>("Name"); 
    nameCol.setCellValueFactory(cell -> cell.getValue().nameProperty()); 
    this.table.getColumns().addAll(nameCol); 

    this.data.add(new Element()); 
    this.table.setItems(this.data); 

    this.executor = Executors.newSingleThreadExecutor(); 
    this.executor.submit(this.changeValues); 
    } 
} 

答えて

1

あなたはJavaFXのためのシングルスレッドのルールに違反している:UIの更新が唯一のFXから作られなければなりませんアプリケーションスレッド。 tick()メソッドはnameProperty()を更新し、テーブルセルはnameProperty()を監視しているため、tick()はUIを更新します。あなたはバックグラウンドスレッドからtick()を呼び出しているので、UIに対するこのアップデートはバックグラウンドスレッドで発生します。結果として生じる動作は本質的に未定義です。

さらに、コードのUI更新のリクエストが多すぎます。したがって、スレッドの問題を修正したとしても、何らかの理由でリクエストを抑制して、FXアプリケーションスレッドに更新するリクエストが多すぎると応答しなくなり、応答しなくなります。

これを行う方法は、Throttling javafx gui updatesで扱われています。私はテーブルモデルクラスのコンテキストで、ここでそれを繰り返すだろう:

import java.util.concurrent.atomic.AtomicInteger; 
import java.util.concurrent.atomic.AtomicReference; 

import javafx.application.Platform; 
import javafx.beans.property.SimpleStringProperty; 
import javafx.beans.property.StringProperty; 

public class Element { 

    // Note that in the example we only actually reference this from a single background thread, 
    // in which case we could just make this a regular int. However, for general use this might 
    // need to be threadsafe. 
    private final AtomicInteger x = new AtomicInteger(0); 

    private final StringProperty nameProperty = new SimpleStringProperty("INIT"); 

    private final AtomicReference<String> name = new AtomicReference<>(); 


    /** This method is safe to call from any thread. */ 
    public void tick() { 
     if (name.getAndSet(Integer.toString(x.incrementAndGet())) == null) { 
      Platform.runLater(() -> nameProperty.set(name.getAndSet(null))); 
     } 
    } 

    public String getName() { 
     return nameProperty().get(); 
    } 

    public void setName(String name) { 
     nameProperty().set(name); 
    } 

    public StringProperty nameProperty() { 
     return nameProperty; 
    } 
} 

ここでの基本的な考え方は、AtomicReference<Stringに「影」の不動産を使用することです。原子的に更新してnullであるかどうかを確認し、そうであればFXアプリケーションスレッドの実プロパティへの更新をスケジュールします。更新では、「シャドー」値をアトミックに取り出し、それをヌルにリセットし、実プロパティを取得値に設定します。これにより、FXアプリケーションスレッドで更新される新しい要求は、FXアプリケーションスレッドがそれらを消費するのと同じ頻度で行われ、FXアプリケーションスレッドがフラッディングされないことが保証されます。もちろん、FXアプリケーションスレッドの更新スケジュールと実際に発生した更新との間に遅延がある場合、更新が行われた場合でも、「シャドウ」値が設定されている最新の値が取得されます。

import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 

import javafx.application.Application; 
import javafx.collections.FXCollections; 
import javafx.collections.ObservableList; 
import javafx.scene.Scene; 
import javafx.scene.control.TableColumn; 
import javafx.scene.control.TableView; 
import javafx.stage.Stage; 

public class FastTableUpdate extends Application { 

    private final ObservableList<Element> data = FXCollections.observableArrayList(); 

    public final Runnable changeValues =() -> { 
     while (true) { 
     if (Thread.currentThread().isInterrupted()) break; 
     data.get(0).tick(); 
     } 
    }; 

    private final ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> { 
     Thread t = new Thread(runnable); 
     t.setDaemon(true); 
     return t ; 
    }); 



    @Override 
    public void start(Stage primaryStage) { 

     TableView<Element> table = new TableView<>(); 
     table.setEditable(true); 

     TableColumn<Element, String> nameCol = new TableColumn<>("Name"); 
     nameCol.setPrefWidth(200); 
     nameCol.setCellValueFactory(cell -> cell.getValue().nameProperty()); 
     table.getColumns().add(nameCol); 

     this.data.add(new Element()); 
     table.setItems(this.data); 

     this.executor.submit(this.changeValues);   

     Scene scene = new Scene(table, 600, 600); 
     primaryStage.setScene(scene); 
     primaryStage.show(); 
    } 

    public static void main(String[] args) { 
     launch(args); 
    } 
} 
+0

どうもありがとう:

ここであなたが示したコントローラのコードと基本的に同等であるスタンドアロンのテストは、です。これは私の問題を解決します:-) –

関連する問題