JavaFX&Multithreading:IllegalStateException在ObservableList.add()

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(); 
+0

@xxSwordy:我推断你的循环是为了_simulate_您的实际延迟;使用这种方法来看看发生了什么。 – trashgod

+0

感谢您提供非常详细的思想答案。这以非常优雅的方式解决了我的问题! – xxSwordy

而是在你执行你的Task<Integer>call()方法的直接更新ObservableList<Integer>的,使用updateValue()发布新的价值,为Task<Canvas>显示here。然后,合适的ChangeListener然后可以在JavaFX Application thread上安全地更新列表,如由@James_D讨论的here所述。