JavaFX&Multithreading:IllegalStateException在ObservableList.add()
我有一个JavaFX应用程序与服务器进行通信并获取数据。接收到的数据被放入ObservableList并显示在TableView中。JavaFX&Multithreading:IllegalStateException在ObservableList.add()
与服务器的通信在自己的线程中运行,调用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>
即使列表已同步,列表仍然会在执行修改的线程上触发事件。在你的情况下,这会导致在非应用程序线程上触发TableView
更新。
而且你不能简单地用Task.updateValue
,看到the javadoc(重点煤矿)的以下部分:
呼叫至
updateValue
被合并和后来的FX应用程序线程[...]和上运行中间值可以合并以保存事件通知。
你需要自己同步它。下面的类融合了更新,以弥补快速更新,可能会通过张贴许多可运行的应用程序线程减缓:
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();
而是在你执行你的Task<Integer>
的call()
方法的直接更新ObservableList<Integer>
的,使用updateValue()
发布新的价值,为Task<Canvas>
显示here。然后,合适的ChangeListener
然后可以在JavaFX Application thread上安全地更新列表,如由@James_D讨论的here所述。
@xxSwordy:我推断你的循环是为了_simulate_您的实际延迟;使用这种方法来看看发生了什么。 – trashgod
感谢您提供非常详细的思想答案。这以非常优雅的方式解决了我的问题! – xxSwordy