Java NIO 仍然阻塞 GUI

huangapple go评论86阅读模式
英文:

Java NIO still blocking GUI

问题

以下是您提供的代码的翻译部分:

编辑:GUI现在弹出(感谢matt),但当我按下启动按钮时,程序完全冻结,我不得不在jGrasp中结束它。

我在使用Java NIO时遇到了一个问题,即当我运行服务器代码时,GUI不会弹出。

以下是代码:

import java.net.*;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.awt.event.*;

public class Server extends JFrame implements ActionListener{

   JButton start = null;

   public Server(){
      JPanel panel = new JPanel(new FlowLayout());
      start = new JButton("Start");
      add(panel);
      panel.add(start);
      start.addActionListener(this);
   }

   public void start(){
      try{
         Selector selector = Selector.open();

         ServerSocketChannel serverChannel = ServerSocketChannel.open();
         serverChannel.configureBlocking(false);

         InetSocketAddress hostAddress = new InetSocketAddress("localhost", 0);
         serverChannel.bind(hostAddress);

         serverChannel.register(selector, SelectionKey.OP_ACCEPT);

         while (true) {
            int readyCount = selector.select();
            if (readyCount == 0) {
               continue;
            }
            // 处理选定的键...
            Set<SelectionKey> readyKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = readyKeys.iterator();
            while (iterator.hasNext()) {
               SelectionKey key = iterator.next();
               // 从集合中删除键,以免重复处理
               iterator.remove();
               // 在通道上操作...
               // 客户端需要连接
               if (key.isAcceptable()) {
                  ServerSocketChannel server = (ServerSocketChannel) key.channel();
                  // 获取客户端套接字通道
                  SocketChannel client = server.accept();
                  // 非阻塞I/O
                  client.configureBlocking(false);
                  // 为读/写操作记录它(这里我们将其用于读取)
                  client.register(selector, SelectionKey.OP_READ);
                  continue;
               }
            }
         }
      }
      catch(IOException ioe){}
   }

   public void actionPerformed(ActionEvent e) {
      if(e.getSource()==start){
         start();
      }
   }

   public static void main(String []args){
      Server gui = new Server();
      gui.setTitle("Server");
      gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      gui.pack();
      gui.setLocationRelativeTo(null);
      gui.setResizable(false);
      gui.setVisible(true);
   }
}

我做错了什么?
我按照这个教程进行了操作,经过简单的调试(Iterator&lt;SelectionKey&gt; iterator = readyKeys.iterator(); 缺少了&lt;SelectionKey&gt;部分),我进行了编译,运行,但是...什么都没有发生。
这是我编写的整个代码,我不明白我做错了什么。

英文:

Edit: GUI now pops up(thanks to matt ), but when I press the start button, the program completely freezes and I have to end it in jGrasp.

I have an issue with java NIO where my GUI doesn't pop-up when I run the server code.

Here is the code:

import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.awt.event.*;
public class Server extends JFrame implements ActionListener{
JButton start = null;
public Server(){
JPanel panel = new JPanel(new FlowLayout());
start = new JButton(&quot;Start&quot;);
add(panel);
panel.add(start);
start.addActionListener(this);
}
public void start(){
try{
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
InetSocketAddress hostAddress = new InetSocketAddress(&quot;localhost&quot;, 0);
serverChannel.bind(hostAddress);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int readyCount = selector.select();
if (readyCount == 0) {
continue;
}
// process selected keys...
Set&lt;SelectionKey&gt; readyKeys = selector.selectedKeys();
Iterator&lt;SelectionKey&gt; iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// Remove key from set so we don&#39;t process it twice
iterator.remove();
// operate on the channel...
// client requires a connection
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel)  key.channel();    
// get client socket channel
SocketChannel client = server.accept();
// Non Blocking I/O
client.configureBlocking(false);
// record it for read/write operations (Here we have used it for read)
client.register(selector, SelectionKey.OP_READ);
continue;
}
}
}
}
catch(IOException ioe){}
}
public void actionPerformed(ActionEvent e) {    
if(e.getSource()==start){
start();
}
}
public static void main(String []args){
Server gui = new Server();
gui.setTitle(&quot;Server&quot;);
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gui.pack();
gui.setLocationRelativeTo(null);
gui.setResizable(false);
gui.setVisible(true);
}
} 

What am I doing wrong here?
I followed this tutorial and after simple debugging(Iterator&lt;SelectionKey&gt; iterator = readyKeys.iterator(); was missing the &lt;SelectionKey&gt; part), I compiledit, ran, and...nothing.
This is the entire code that I wrote and I don't understand what I'm doing wrong.

答案1

得分: 0

你需要将任务分开。一个是启动 GUI,另一个是启动服务器。

将其中一个任务设为服务器。

public void runServer() throws Exception{
    Selector selector = Selector.open();
    ServerSocketChannel serverChannel = ServerSocketChannel.open();
    serverChannel.configureBlocking(false);

    InetSocketAddress hostAddress = new InetSocketAddress("localhost", 0);
    serverChannel.bind(hostAddress);

    serverChannel.register(selector, SelectionKey.OP_ACCEPT);

    while (true) {
        int readyCount = selector.select();
        if (readyCount == 0) {
            continue;
        }
        Set<SelectionKey> readyKeys = selector.selectedKeys();
        Iterator<SelectionKey> iterator = readyKeys.iterator();
        while (iterator.hasNext()) {
            SelectionKey key = iterator.next();
            iterator.remove();
            if (key.isAcceptable()) {
                ServerSocketChannel server = (ServerSocketChannel) key.channel();
                // 获取客户端 socket 通道
                SocketChannel client = server.accept();
                // 非阻塞 I/O
                client.configureBlocking(false);
                // 记录它以进行读/写操作(这里我们将其用于读取)
                client.register(selector, SelectionKey.OP_READ);
                continue;
            }
        }
    }
}

然后将另一个任务设置为 GUI。

public void startGui(){
    JPanel panel = new JPanel(new FlowLayout());
    JButton start = new JButton("Start");
    add(panel);
    panel.add(start);
    setTitle("Server");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    pack();
    setLocationRelativeTo(null);
    setResizable(false);
    setVisible(true);
}

现在你的主方法可以简化为:

public static void main(String[] args) throws Exception{
    Server server = new Server();
    EventQueue.invokeAndWait(server::startGui);
    server.runServer();
}

这样,GUI 在 EDT 上启动,而永远不会结束的服务器循环将占用主线程。还有一个小改变。不要扩展 JFrame,只需在 startGui 方法中创建一个 JFrame。这样所有 GUI 工作都在 EDT 上完成。

另外,我还删除了你的异常处理并使方法抛出异常。这样你会看到堆栈跟踪。

关于你新的问题,为什么 GUI 会冻结。这是因为你在 EDT 上阻塞。你的 start() 方法永远不会退出。最简单的修复方法是:

public void actionPerformed(ActionEvent e) {    
    if (e.getSource() == start) {
        new Thread(this::start).start();
    }
}

这将启动一个新线程来运行你的服务器。请注意最明显的问题,如果你点击“开始”超过一次,将会启动多个线程!

英文:

You need to separate your tasks. One is to start a gui, and the other one is to start a server.

Make one task the server.

public void runServer() throws Exception{
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
InetSocketAddress hostAddress = new InetSocketAddress(&quot;localhost&quot;, 0);
serverChannel.bind(hostAddress);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int readyCount = selector.select();
if (readyCount == 0) {
continue;
}
Set&lt;SelectionKey&gt; readyKeys = selector.selectedKeys();
Iterator&lt;SelectionKey&gt; iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel)  key.channel();    
// get client socket channel
SocketChannel client = server.accept();
// Non Blocking I/O
client.configureBlocking(false);
// record it for read/write operations (Here we have used it for read)
client.register(selector, SelectionKey.OP_READ);
continue;
}
}
}
}
}

Then make the other task the gui.

public void startGui(){
JPanel panel = new JPanel(new FlowLayout());
JButton start = new JButton(&quot;Start&quot;);
add(panel);
panel.add(start);
setTitle(&quot;Server&quot;);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setResizable(false);
setVisible(true);
}

Now your main method can be reduced to.

public static void main(String[] args) throws Exception{
Server server = new Server();
EventQueue.invokeAndWait( server::startGui );
server.runServer();
}

This way, the gui is started on the EDT, and the server loop which never finishes takes up the main thread. Another small change. Don't extend JFrame, just create a JFrame in the startGui method. That way all gui work is done on the EDT.

Not that I have also removed your Exception handling and just made the methods throw Exception. That way you'll see a StackTrace.

In regards to your newer question, why is the gui freezing. It's because your blocking on the EDT. Your start() method never exits. The most crude method to fix this is to.

public void actionPerformed(ActionEvent e) {    
if(e.getSource()==start){
new Thread( ()-&gt;start();).start();
}

}

What that will do, is start a new thread to run your server. Notice the most obvious problem, if you click start more than once, it start more than one thread!

答案2

得分: 0

在这种情况下,可能并不需要使用非阻塞 I/O。相反,主线程可以继续执行 I/O(使用传统的阻塞调用),同时使用 SwingWorker 安排更新到 UI。这些更新将在 Swing 事件分派线程中发生,所以您甚至不需要担心创建自己的线程。

非阻塞 I/O 允许您检查通道是否准备好执行某些操作,而无需阻塞。如果没有 I/O 操作准备好,线程可以执行其他任务,稍后再进行检查。

但在您的程序中,您编写的代码实际上在一个“忙循环”中等待操作准备就绪。因为构造函数从不返回,甚至不会调用 setVisible() 方法。您从未让线程有机会执行任何其他工作,包括绘制 UI。

此外,使用 Java Swing 时,有一个专用的线程执行所有对 UI 的更新。当您创建组件或更新它们显示的内容时,必须使用该线程进行工作。Swing 提供了 SwingWorker 类来安排操纵 UI 的短任务。您可以通过完成《Swing 中的并发》教程来了解有关 Swing 中并发的更多信息。

英文:

In this case, using non-blocking I/O is probably not necessary. Instead, the main thread could continue to perform I/O (using traditional blocking calls), while scheduling updates to the UI with SwingWorker. These updates will occur in the Swing Event Dispatch Thread, so you don't even need to worry about creating your own threads.

Non-blocking IO allows you to check whether channels are ready to perform certain operations without blocking. If no I/O operations are ready, the thread can perform other tasks and check back later.

But in your program, you've written code that effectively waits (in a "busy-loop") until the operations are ready. Because the constructor never returns, the call to setVisible() is never even invoked. You never give the thread a chance to do any other work, including painting the UI.

Further, with Java Swing, there is a special thread that performs all updates to the UI. When you create components or update what they display, that work has to be done with that thread. Swing provides the SwingWorker class to schedule short tasks that manipulate the UI. You can learn more about concurrency in Swing by completing the <i>Concurrency in Swing</i> tutorial.

huangapple
  • 本文由 发表于 2020年4月7日 22:27:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/61082350.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定