JavaFX&多线程:ObservableList.add()上的IllegalStateException

时间:2017-11-11 01:47:09

标签: java multithreading javafx task

我有一个与服务器通信并从服务器获取数据的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>

2 个答案:

答案 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