我有一个与服务器通信并从服务器获取数据的JavaFX应用程序。接收的数据放入ObservableList并显示在TableView中。
与服务器的通信在其自己的Thread中运行,并且在调用ObservableList.add时,它会触发IllegalStateException(抱怨不是事件线程/不是JavaFX线程)
我发现以下solution类似的问题,但我不知道如何采用这个,因为在我的情况下,与服务器的通信需要不断保持,所以任务/线程是一直运行直到通讯终止。
我在这里有一个最小的工作示例,它会触发所述异常并粗略地模拟应用程序的工作方式。
主要
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
控制器:
package sample;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
public class Controller {
public TableView<Integer> timeTable;
public TableColumn<Integer, String> positionColumn;
private ObservableList<Integer> testList;
@FXML
private void initialize() {
testList = FXCollections.synchronizedObservableList(FXCollections.observableArrayList());
positionColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().toString()));
timeTable.setItems(testList);
Task<Integer> integerTask = new Test(testList);
Thread testThread = new Thread(integerTask);
testThread.start();
}
}
沟通任务:
package sample;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
public class Test extends Task<Integer> {
private ObservableList<Integer> testlist;
Test(ObservableList<Integer> list) {
testlist = list;
}
@Override
protected Integer call() throws Exception {
// Emulates the server communication thread. Instead of an endless loop, I used a fixed number of iterations.
// The real application has an endless while loop for server communication so a Task cannot be used to
// get the data
// getDataFromServer()
// parseData()
// putDataInList()
// loop
Thread.sleep(2000);
for (int i = 0; i < 500; ++i) {
testlist.add(i);
}
return 0;
}
}
FXML:
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:controller="sample.Controller"
xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
<TableView fx:id="timeTable" editable="true" prefHeight="498.0"
prefWidth="254.0">
<columns>
<TableColumn fx:id="positionColumn" prefWidth="73.0" text="Position"/>
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
</TableView>
</GridPane>
答案 0 :(得分:3)
即使列表是同步的,列表的修改仍会触发执行修改的线程上的事件。在您的情况下,这会导致在非应用程序线程上触发TableView
更新。
此外,您不能简单地使用Task.updateValue
,请参阅the javadoc的以下部分(强调我的):
updateValue
的呼叫合并,稍后在FX应用程序线程[...]上运行,中间值可以合并以节省事件通知
您需要自己同步。以下类组合更新以补偿快速更新,这些更新可能通过发布许多runnable来减慢应用程序线程:
public abstract class JavaFXWorker<T> implements Runnable {
private final List<T> results = new LinkedList<>();
private final Object lock = new Object();
private boolean updateWaiting = false;
protected void publish(T... values) {
synchronized (lock) {
for (T v : values) {
results.add(v);
}
// don't trigger additional updates, if last update didn't fetch the results yet
// this reduces the number of Runables posted on the application thread
if (!updateWaiting) {
updateWaiting = true;
Platform.runLater(this::update);
}
}
}
private void update() {
List<T> chunks;
synchronized(lock) {
// copy results to new list and clear results
chunks = new ArrayList(results);
results.clear();
updateWaiting = false;
}
// run ui updates
process(chunks);
}
protected abstract void process(List<T> chunks);
}
您的代码可以使用上面的类重写如下。
public class Test extends JavaFXWorker<Integer> {
private final ObservableList<Integer> testlist;
public Test(ObservableList<Integer> list) {
testlist = list;
}
@Override
public void run() {
// Emulates the server communication thread. Instead of an endless loop, I used a fixed number of iterations.
// The real application has an endless while loop for server communication so a Task cannot be used to
// get the data
// getDataFromServer()
// parseData()
// putDataInList()
// loop
Thread.sleep(2000);
for (int i = 0; i < 500; ++i) {
publish(i);
}
}
@Override
protected process(List<Integer> chunks) {
testlist.addAll(chunks);
}
}
testList = FXCollections.observableArrayList();
...
Thread testThread = new Thread(new Test(testList));
testThread.start();
答案 1 :(得分:2)
不要直接在Task<Integer>
的<r>
<title highlight="yes">Computing in 2017</title>
<date>27/09/2013</date>
</r>
方法实施中更新ObservableList<Integer>
,而是使用call()
发布新值,如图所示{{3} } updateValue()
。然后,合适的Task<Canvas>
可以安全地更新here上的列表,如@James_D所讨论的JavaFX Application thread。