"Not on FX application thread" Netty 和 JavaFX 异常

"Not on FX application thread" exception with Netty and JavaFX

我正在创建一个 Netty/JavaFX 应用程序,当我尝试从客户端向服务器发送图片时遇到以下异常。

Exception in thread "nioEventLoopGroup-3-1" java.lang.IllegalStateException: Not on FX application thread; currentThread = nioEventLoopGroup-3-1
    at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:204)
    at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:364)
    at javafx.scene.Scene.addToDirtyList(Scene.java:485)
    at javafx.scene.Node.addToSceneDirtyList(Node.java:424)
    at javafx.scene.Node.impl_markDirty(Node.java:415)
    at javafx.scene.Node.notifyParentsOfInvalidatedCSS(Node.java:8709)
    at javafx.scene.Node.requestCssStateTransition(Node.java:8639)
    at javafx.scene.Node.pseudoClassStateChanged(Node.java:8680)
    at javafx.scene.Node$FocusedProperty.markInvalid(Node.java:7535)
    at javafx.scene.Node$FocusedProperty.store(Node.java:7520)
    at javafx.scene.Node.setFocused(Node.java:7578)
    at javafx.scene.control.TableRow.updateFocus(TableRow.java:305)
    at javafx.scene.control.TableRow.lambda$new(TableRow.java:106)
    at javafx.scene.control.TableRow$$Lambda7/862831050.invalidated(Unknown Source)
    at javafx.beans.WeakInvalidationListener.invalidated(WeakInvalidationListener.java:83)
    at com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:349)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
    at javafx.beans.property.ReadOnlyObjectWrapper$ReadOnlyPropertyImpl.fireValueChangedEvent(ReadOnlyObjectWrapper.java:176)
    at javafx.beans.property.ReadOnlyObjectWrapper.fireValueChangedEvent(ReadOnlyObjectWrapper.java:142)
    at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112)
    at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:145)
    at javafx.scene.control.TableView$TableViewFocusModel.setFocusedCell(TableView.java:2981)
    at javafx.scene.control.TableView$TableViewFocusModel.focus(TableView.java:3022)
    at javafx.scene.control.TableView$TableViewSelectionModel.focus(TableView.java:1941)
    at javafx.scene.control.TableView$TableViewSelectionModel.focus(TableView.java:1935)
    at javafx.scene.control.TableView$TableViewSelectionModel.focus(TableView.java:1918)
    at javafx.scene.control.TableView$TableViewArrayListSelectionModel.clearSelection(TableView.java:2554)
    at javafx.scene.control.TableView$TableViewArrayListSelectionModel.onChanged(TableView.java:2036)
    at javafx.collections.WeakListChangeListener.onChanged(WeakListChangeListener.java:88)
    at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:329)
    at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
    at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
    at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
    at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
    at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
    at javafx.collections.ModifiableObservableListBase.remove(ModifiableObservableListBase.java:183)
    at network.ServerHandler.handlerRemoved(ServerHandler.java:53)
    at io.netty.channel.DefaultChannelPipeline.callHandlerRemoved0(DefaultChannelPipeline.java:527)
    at io.netty.channel.DefaultChannelPipeline.callHandlerRemoved(DefaultChannelPipeline.java:521)
    at io.netty.channel.DefaultChannelPipeline.remove0(DefaultChannelPipeline.java:351)
    at io.netty.channel.DefaultChannelPipeline.destroyDown(DefaultChannelPipeline.java:798)
    at io.netty.channel.DefaultChannelPipeline.destroyUp(DefaultChannelPipeline.java:767)
    at io.netty.channel.DefaultChannelPipeline.destroy(DefaultChannelPipeline.java:759)
    at io.netty.channel.DefaultChannelPipeline.fireChannelUnregistered(DefaultChannelPipeline.java:743)
    at io.netty.channel.AbstractChannel$AbstractUnsafe.run(AbstractChannel.java:615)
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:380)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:357)
    at io.netty.util.concurrent.SingleThreadEventExecutor.run(SingleThreadEventExecutor.java:116)
    at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:137)
    at java.lang.Thread.run(Thread.java:745)

当我将我的应用程序作为 javafx 应用程序启动时抛出此异常。当我将其作为控制台应用程序启动时,客户端与服务器断开连接但未显示任何错误或异常。

客户处理程序:

class ClientHandler extends ChannelInboundMessageHandlerAdapter<Object> {

    @Override
    public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
        MessageWrapper message = (MessageWrapper) msg;
        System.out.println(message);
        switch (message.getHeader()) {
            case "IMG":
                takeAndSendScreenShot();
            default:;
        }
    }

    private void takeAndSendScreenShot() throws IOException {
//        try {
//            //originalImage = new Robot().createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
//
//        } catch (AWTException ex) {
//            Logger.getLogger(ClientHandler.class.getName()).log(Level.SEVERE, null, ex);
//        }

        BufferedImage originalImage = ImageIO.read(new File("c:\test.jpg"));
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(originalImage, "jpg", baos);
        baos.flush();
        byte[] imageInByte = baos.toByteArray();
        baos.close();
        Client.getChannel().write(new MessageWrapper("IMG", baos));
//      Client.getChannel().write(new MessageWrapper("TEST", "TEST"));

    }
}

服务器: public class 服务器 {

private static final Logger log = Logger.getLogger(Server.class.getName());

private final int port;

public Server(int port) {
    this.port = port;
}

public void run() throws InterruptedException {
    EventLoopGroup mainGroup = new NioEventLoopGroup();
    EventLoopGroup slaveGroup = new NioEventLoopGroup();

    try {
        ServerBootstrap bootstarp = new ServerBootstrap()
                .group(mainGroup, slaveGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ServerInitaizer());
        bootstarp.bind(port).sync().channel().closeFuture().sync();
    } finally {
        mainGroup.shutdownGracefully();
        slaveGroup.shutdownGracefully();
    }
}

} 服务器初始化:

public class ServerInitaizer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();

//        pipeline.addLast("decoder", new ObjectDecoder(ClassResolvers.softCachingResolver(ClassLoader.getSystemClassLoader())));
        pipeline.addLast("decoder", new ObjectDecoder(ClassResolvers.weakCachingConcurrentResolver(ClassLoader.getSystemClassLoader())));
        pipeline.addLast("encoder", new ObjectEncoder());
        pipeline.addLast("handler", new ServerHandler());

    }
}

服务器处理程序:

public class ServerHandler extends SimpleChannelInboundHandler<Object> {

    private static final Logger log = Logger.getLogger(Server.class.getName());

    private static final ChannelGroup channels = new DefaultChannelGroup(
            GlobalEventExecutor.INSTANCE);

    private static final ObservableList<Client> clients
            = FXCollections.observableArrayList(
                    (Client c) -> new Observable[]{c.userNameProperty(), c.remoteAddressProperty()});

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        log.info(ctx.toString());
        Channel incoming = ctx.channel();
        channels.add(incoming);
        clients.add(new Client(incoming.remoteAddress().toString(), ""));
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = null;
        log.info(ctx.toString());
        try {
            incoming = ctx.channel();

            for (int i = clients.size() - 1; i > -1; i--) {
                if (incoming.remoteAddress().toString().equals(clients.get(i).getRemoteAddress())) {
                    clients.remove(i);
                }
            }
        } finally {
            channels.remove(incoming);
        }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        log.info(msg.toString());
        Channel incoming = ctx.channel();
        MessageWrapper message = (MessageWrapper) msg;

        switch (message.getHeader()) {
            case "MSG":
                saveMessage(incoming, message.getContent().toString());
            case "USER":
                setUser(incoming, message.getContent().toString());
            default:;
        }
    }

消息包装器:

@Data
public class MessageWrapper implements Serializable{
    private static final long serialVersionUID = 1L;
    String header;
    Object content;

    public MessageWrapper(String message, Object content) {
        this.header = message;
        this.content = content;
    }


}

Main.java:

public class Main extends Application {

    public static void main(String[] args) throws InterruptedException {
        launch(args);
        //new Server(8000).run();
    }

    @Override
    public void start(Stage stage) throws Exception {

        Task<Integer> task = new Task<Integer>() {
            @Override
            protected Integer call() {
                try {
                    new Server(8000).run();
                } catch (Exception e) {
                    System.out.println(e);
                }
                return 1;
            }
        };

        Thread th = new Thread(task);
        th.setDaemon(true);
        System.out.println("Starting server task...");
        th.start();

        Parent root = FXMLLoader.load(getClass().getResource("/fxml/TablePanel.fxml"));
        Scene scene = new Scene(root);
        stage.setTitle("FXML Welcome");
        stage.setScene(scene);
        stage.show();
    }

}

如果有人能告诉我问题出在哪里,或者我怎样才能获得有关此问题的更多信息。我真的很感激。

您有一个用作 JavaFX 控件模型的 ObservableList,并且您正在从 Netty 线程更新该列表。这会导致 JavaFX 尝试从 Netty 线程更新 UI,这是不允许的(所有 UI 操作都需要在应用程序线程中执行)。您需要使用 Platform.runLater() 将执行转移到应用程序线程。