在GUI

时间:2015-12-30 22:02:03

标签: java multithreading javafx fxml

我正在从编写Java Swing应用程序转换到JavaFX,以编写基于Java的现代GUI应用程序。

我想知道创建基于网络的可重用线程服务的最佳方法。我编写网络服务的方式是使用控制器类(通过Net-beans GUI从FXML生成)。我通过一个名为' transmitter'的私人服务成员将线程逻辑放在这里。我通过开始/停止按钮的事件回调来连接启动/停止逻辑 基于网络的线程实现为javafx服务 - 我这样做是因为我想在目标地址更改时重新启动服务/线程。这似乎是建议的方法,而不是独立的任务。

现在网络服务非常简单,它所做的就是使用一些GUI小部件来配置数据包,每秒一次传输到主机/端口。我只需要在主机/端口小部件更改时重新启动服务,但是如果网络服务正在运行,我想在不中断/重新启动DatagramSocket的情况下修改数据包。我有疑问并需要一些指导的地方是:

  1. 在网络中线程化网络线程的推荐方法是什么? 基于FXML的应用程序?非常感谢一个例子。
  2. 如何安全地从GUI小部件传递更改(通过他们的 动作执行回调)到正在运行的服务类?
  3. 以下显示的是我的控制器类中最相关的部分:

    /**
     * FXML Controller class
     *
     * @author johnc
     */
    public class OpMessageServerController implements Initializable {
        @FXML
        private Text mCurrentDateTimeText;
        @FXML
        private Label mApplicationStatus;
        @FXML
        private ComboBox<DiscreteStatus> mPofDS;
        @FXML
        private ComboBox<PhaseOfFlightFMS> mPofFMS;
        @FXML
        private ComboBox<DiscreteStatus> mTailNumberDS;
        @FXML
        private ComboBox<DiscreteStatus> mConfigTableDS;
        @FXML
        private ComboBox<DiscreteStatus> mDateTimeDS;
        @FXML
        private TextField mEpicPN;
        @FXML
        private TextField mConfigTablePNHash;
        @FXML
        private TextField mTailNumber;
        @FXML
        private ComboBox<DiscreteStatus> mTopLevelPNDS;
        @FXML
        private Button mStartStopButton;
        @FXML
        private ComboBox<String> mDLMUHostSpec;
        @FXML
        private CheckBox connectionStatusC1;
        @FXML
        private CheckBox wsuConnectionStatus;
        @FXML
        private CheckBox connectionStatusC4;
        @FXML
        private CheckBox connectionStatusC3;
        @FXML
        private CheckBox connectionStatusC2;
        @FXML
        private CheckBox dlmuwConnectionStatus;
    
        private Service<Void> transmitter;
    
    
    
        /**
         * Initializes the controller class.
         * @param url
         * @param rb
         */
        @Override
        public void initialize(URL url, ResourceBundle rb) {        
            mPofDS.setItems(FXCollections.observableArrayList(DiscreteStatus.values()));
            mPofDS.getSelectionModel().selectFirst();
            mPofFMS.setItems(FXCollections.observableArrayList(PhaseOfFlightFMS.values()));
            mPofFMS.getSelectionModel().selectFirst();
            mTailNumberDS.setItems(FXCollections.observableArrayList(DiscreteStatus.values()));
            mTailNumberDS.getSelectionModel().selectFirst();
            mConfigTableDS.setItems(FXCollections.observableArrayList(DiscreteStatus.values()));
            mConfigTableDS.getSelectionModel().selectFirst();
            mDateTimeDS.setItems(FXCollections.observableArrayList(DiscreteStatus.values()));
            mDateTimeDS.getSelectionModel().selectFirst();
            mTopLevelPNDS.setItems(FXCollections.observableArrayList(DiscreteStatus.values()));
            mTopLevelPNDS.getSelectionModel().selectFirst();
    //      mDLMUHostSpec.setItems(FXCollections.observableArrayList(
    //          FXCollections.observableArrayList("localhost:1234", "192.168.200.2:1234")));
    
            // add event handler here to update the current date/time label
            // this should also update the transmit datastructure
            final Timeline timeline = new Timeline(new KeyFrame(
                Duration.seconds(1), (ActionEvent event) -> {
                LocalDateTime currentDateTime = LocalDateTime.now();
                mCurrentDateTimeText.setText(currentDateTime.format(
                    DateTimeFormatter.ofPattern("kk:mm:ss uuuu")));  
            }));  
    
            timeline.setCycleCount(Animation.INDEFINITE);  
            timeline.play();
    
            // create a service.
            transmitter = new Service() {
                @Override 
                protected Task createTask() {
                    return new Task<Void>() {
                        @Override 
                        protected Void call() throws InterruptedException {
                            updateMessage("Running...");
                            updateProgress(0, 10);
                            DatagramSocket sock = null;
                            while (!isCancelled()) {
                                try {
                                    if (sock == null) {
                                        DatagramSocket sock = new DatagramSocket();
                                    }
                                } catch (SocketException ex) {
                                    Logger.getLogger(OpMessageServerController.class.getName()).log(Level.SEVERE, null, ex);
                                }
                                //Block the thread for a short time, but be sure
                                //to check the InterruptedException for cancellation
                                OpSupportMessage opSupportMessage = new OpSupportMessage(
                                    DiscreteStatus.NormalOperation, 
                                    PhaseOfFlightFMS.Cruise, 
                                    DiscreteStatus.NormalOperation, 
                                    "TAILNUM",
                                    DiscreteStatus.NormalOperation);
                                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                                String[] specParts = mDLMUHostSpec.getValue().split(":");
                                if (specParts.length == 2) {
                                    try {
                                        opSupportMessage.write(bos);
                                        byte[] buff = bos.toByteArray();
                                        DatagramPacket packet = new DatagramPacket(
                                            buff, buff.length, InetAddress.getByName(
                                            specParts[0]), Integer.parseInt(specParts[1]));
                                        mSocket.send(packet);
                                        Thread.sleep(1000);
                                    } catch (IOException ex) {
                                    } catch (InterruptedException interrupted) {
                                        if (isCancelled()) {
                                            updateMessage("Cancelled");
                                            break;
                                        }
                                    }
                                }
                            }                        
                            updateMessage("Cancelled");
                            return null;
                        }
    
                        @Override
                        protected void succeeded() {
                            System.out.println("Scanning completed.");
                        }
    
                        @Override 
                        protected void failed() {
                            System.out.println("Scanning failed.");
                        }
    
                        @Override 
                        protected void running() {
                            System.out.println("Scanning started.");
                        }
    
                        @Override 
                        protected void cancelled() {
                             System.out.println("Scanning cancelled.");
                        }                    
    
                        private void DatagramSocket() {
                            throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
                        }
                    };
                }
            };
    
            mApplicationStatus.textProperty().bind(transmitter.messageProperty());
    
        };
    
    @FXML
    private void startStopButtonAction(ActionEvent event) {
        if (!transmitter.isRunning()) {
            transmitter.reset();
            transmitter.start();
        }                
    }
    
     …
    
    }
    

2 个答案:

答案 0 :(得分:3)

背景

这个答案是基于对问题的一系列评论,它有点摇摆,没有提供针对问题中代码的解决方案,也没有解决问题中的一些概念,如低级UDP基于套接字的通信系统 - 为此道歉。

示例解决方案项目

我使用基于Web套接字的通信做了一个JavaFX应用程序概念证明:javafx-websocket-test。也许那里的一些概念可能会对您有所帮助,特别是使用它的client JavaFX Task and Service codesample client application and controller

该项目确实在可执行的实现中演示了James_D链接的Adam Bien's article on JavaFX Integration Strategies中概述的一些通信原则,例如:

  1. 在JavaFX服务中设置Web套接字端点。
  2. 在异步JavaFX任务中包装每个通信交互。
  3. 使用异步事件回调将成功和失败结果分流回UI。
  4. 此外,该示例显示了网络服务和JavaFX UI之间的交互,JavaFX UI向服务发出异步请求并处理来自它的异步响应。

    我确实记得看似简单的Java Web套接字API确实包含了一些问题。它只是一个概念证明,因此请小心使用它作为强大的网络服务的基础。

    评论和想法

    由于以下原因,这实际上是回答IMO的难题:

    1. 有许多形式的网络通信,其中一些适用于不同的应用程序。
    2. 目前没有将网络服务与JavaFX应用程序集成的标准或最佳实践。
    3. 通过UI状态监控和异常处理提供强大的网络连接通常不像看起来那么简单,容易出错。
    4. 有许多细微之处需要处理,例如:

      • 如果发生通讯失败该怎么办?
      • 如果应用程序以比网络或服务器可以处理的更快的速度发出请求,该怎么办?
      • 如果用户在消息未完成时关闭应用程序会发生什么?
      • 如何确保在冗长的通信过程中不冻结UI?
      • 如何提供长时间网络处理正在进行的UI反馈?
      • 正在使用什么基础通信技术?
      • 底层沟通是有状态还是无国籍?
      • 通讯是非阻塞和事件驱动还是阻止?
      • 如何序列化和反序列化数据以进行传输?

      即使单一尺寸适合所有通信模式也很困难,但标准是&#34;可以调整适合许多需求的通信模型。例如,类似于基于浏览器的网络模型中的http ajax callsNetConnections for flash。这些似乎足以满足各种各样的需求。虽然它们当然不是最佳选择,否则就不会创建诸如网络套接字或http直播之类的替代系统。

      理想情况下,对于JavaFX client =&gt;,会有一个标准化的API,例如jQuery.ajax()。服务器通信,但我还没有看到任何人创建JavaFX相当于那种API。

      与其他核心JavaFX API不同,这种用于网络通信的标准化高级接口目前尚未以现成的形式存在。但是,有许多库和函数可用作开发自己服务的基本构建块;也许甚至太多而不能合理地处理。

      请注意,大多数更高级别的网络协议库(例如Tyrus web socket implementationApache HTTP components基础JAX-RS provider都有自己的内部线程池进行通信。像netty这样的系统基于nio并且是事件驱动而不是线程管理。您的JavaFX网络客户端服务是以下两种方式之一:

      • 对于non-blocking I/O,它发出异步调用,挂钩响应事件并通过Platform.runLater将它们转发回JavaFX。
      • 对于阻塞I / O,它使用一个带有隐式或显式执行程序服务池的Task或Service生成一个线程来管理UI交互,但不管理实际的网络通信。

      一个关键且令人困惑的事情是JavaFX应用程序代码应始终以异步方式执行网络通信。对于非阻塞I / O,调用已经是异步,因此不一定需要包装器任务。对于阻止I / O,您不希望阻止UI线程,因此在其自己的线程中运行的Task包装器可以防止发生这种情况。

      有人会认为这会使非阻塞I / O调用变得更简单,但事实并非如此,因为JDK的非阻塞I / O API水平相当低且相当棘手编码到。它不适合高级应用程序代码。

      通常,应用程序代码最好使用更高级别的库,例如JAX-RS,Web套接字或akka(或者,最好是它们之上的一个层),它们在内部管理阻塞或通信的细节。非阻塞方式并提供用于发送和接收消息的事件驱动API。各个消息事件可以包装在JavaFX Task中以进行异步处理。因此,从JavaFX应用程序的角度来看,一切都是事件驱动的,没有任何阻塞,并且无论底层通信协议和阻塞/非阻塞通信基础设施如何,相同的应用程序API都能正常工作。

        

      感谢概念验证应用,这将非常有用,但有一点有点模糊,就是如何安全地将GUI更改安全地传递给正在运行的服务线程。 HelloService似乎使用了&#39;名称&#39;简单的字符串属性,用于在启动之前将更改从GUI传递到服务。我想知道如何以线程安全的方式将UI更改传递给正在运行的后台服务。通过某种类型或消息api或许?

      具有固定最大大小的BlockingQueue在队列已满时拒绝其他请求,可用于从基于JavaFX线程的代码到消费者服务的通信。它是经典producer-consumer problem的一个相当优雅的解决方案。

      当然,你可以跳过阻塞队列并继续创建异常任务ad-nauseum,这对于低容量通信来说很好,但是可能导致有限的线程资源缺乏以进行高容量通信。处理这种情况的一种标准方法是使用来自管理线程池的ExecutorServiceExecutors。执行程序服务的线程池可以定义为bounded to a max number of threads,并在内部使用无限制队列,如果所有线程都忙,则消息堆积起来。这样您就不需要定义自己的阻塞队列,只需发出异步服务请求,如果可以,则立即在线程中处理它们,如果不能,请求会在内部队列中堆积。

      这实际上是JavaFX Service的工作方式:

        

      默认情况下,服务使用具有一些未指定的默认或最大线程池大小的线程池Executor。这样做是为了使天真的代码不会通过创建数千个线程来完全淹没系统。

      和:

        

      如果在服务上指定了Executor,那么它将用于实际执行服务。否则,将创建并执行守护程序线程。如果您希望创建非守护程序线程,请指定自定义执行程序(例如,您可以使用带有自定义ThreadFactory的ThreadPoolExecutor)。

      简单的BlockedQueue消息传递不适合的更复杂的解决方案将使用基于主题的消息队列样式解决方案,例如基于Java的STOMP客户端,例如此kaazing example

      将消息信息提供给服务只是要求的一部分,本质上是执行异步消息发送。您还需要处理返回的响应。要做到这一点,有两种选择:

      1. 您将每个请求建模为单独的任务,onSuccess和onError处理程序处理任务响应。在服务中运行任务可确保它由具有内部队列支持的固定线程池的执行程序处理以进行溢出。
      2. 您使用自己的API编写自己的长期运行服务接口,并使用Platform.runLater封装请求的阻塞队列,以便将通信结果传回UI。
      3. 要使响应处理程序逻辑动态化并可由调用者调整,您可以将处理程序函数作为lambda函数传递,以便在使用Platform.runLater进行原始调用时成功执行。

        如果将调用包装在Task或Service中,并使用onSucceeded函数,则不需要runLater调用,因为实现将确保在任务完成后在JavaFX线程上调用onSucceeded处理程序

        请注意,网络请求和响应通常需要对可序列化流中的数据编组和解组进行一些转换。一些更高级别的网络API(如JAX-RS或Web套接字提供程序)提供了接口和实用程序,可以为您执行某些工作,通常使用特定库进行不同类型的转换,例如JAXB用于XML序列化Jackson用于JSON序列化。

        略有关联的信息和进一步的想法

        接下来可能有点偏离主题,但这是BlockingQueue and Task interaction的一个示例,它不是网络服务,但它确实演示了在生产者/消费者情况下使用队列,具有被动UI和进展监测。

        另一件令人感兴趣的事情(至少对我而言)是基于Akka的JavaFX客户端与服务器通信解决方案。这似乎是传统的http / rest / soap / rmi调用或基于消息队列的处理的一个很好的替代方案。 Akka本质上是一个基于事件的容错异步并发通信解决方案,因此它似乎是基于UI的框架(如JavaFX)的良好匹配,允许开发人员在适当的抽象层进行处理。但我还没有看到一个依赖于Akka的基于JavaFX的消息传递客户端。

答案 1 :(得分:2)

  

我想知道创建基于网络的最佳方法   可重复使用的线程服务。我编写网络服务的方式是   使用控制器类(通过Net-bean从FXML生成)   GUI)。我通过私人服务成员将线程逻辑放在这里   命名的发射器&#39;我通过连线启动/停止逻辑   开始/停止按钮的事件回调。

我谦卑地建议您将网络服务和GUI控制器作为单独的项目进行开发。

我希望网络服务在自己的容器或虚拟机中作为守护程序/后台线程运行。该组织的优势在于它使您的服务器远离JavaFX事件循环和应用程序线程的变幻莫测。您将要设计服务以识别管理命令和/或中断来自控制器的请求。您可以将您的网络服务开发为REST或任何您想要的内容,而无需知道如何将其转换为JavaFX应用程序线程。

然后,我将GUI控制器作为单独的GUI应用程序在同一进程中运行,或者,如果需要远程管理,则在单独的JVM中运行(并使用IPC发送/接收管理消息)。

TL; DR:如果是我,我会抵制将网络服务编程为JavaFX应用程序的诱惑。