Java NIO 仍然阻塞 GUI
Java NIO still blocking GUI
编辑:GUI 现在弹出(感谢 matt ),但是当我按下开始按钮时,程序完全冻结,我必须在 jGrasp 中结束它。
我遇到 java NIO 的问题,当我 运行 服务器代码时,我的 GUI 没有弹出。
代码如下:
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;
}
// process selected keys...
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// Remove key from set so we don'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("Server");
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gui.pack();
gui.setLocationRelativeTo(null);
gui.setResizable(false);
gui.setVisible(true);
}
}
我在这里做错了什么?
我按照 this 教程进行了简单的调试(Iterator<SelectionKey> iterator = readyKeys.iterator();
缺少 <SelectionKey>
部分),我编译了它,运行,然后......什么都没有。
这是我写的全部代码,我不明白我做错了什么。
你需要分开你的任务。一个是启动一个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();
// 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;
}
}
}
}
}
然后将其他任务设为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();
}
这样,在EDT上启动了gui,永不结束的服务器循环占用了主线程。另一个小变化。不要扩展JFrame,只是在startGui 方法中创建一个JFrame。这样 所有 gui 工作都在 EDT 上完成。
并不是说我也删除了您的异常处理,只是让方法抛出异常。这样你就会看到一个 StackTrace。
关于您的新问题,为什么 gui 会冻结。这是因为你在 EDT 上阻塞。您的 start()
方法永远不会退出。解决这个问题的最粗略的方法是。
public void actionPerformed(ActionEvent e) {
if(e.getSource()==start){
new Thread( ()->start();).start();
}
}
这样做的目的是启动一个新线程到 运行 您的服务器。注意最明显的问题,如果你点击启动不止一次,它会启动多个线程!
在这种情况下,可能没有必要使用非阻塞 I/O。相反,主线程可以继续执行 I/O(使用传统的阻塞调用),同时使用 SwingWorker
调度对 UI 的更新。这些更新将发生在 Swing 事件调度线程中,因此您甚至不必担心创建自己的线程。
非阻塞 IO 允许您在不阻塞的情况下检查通道是否准备好执行某些操作。如果没有 I/O 操作准备就绪,线程可以执行其他任务,稍后再回来查看。
但是在您的程序中,您编写的代码可以有效地等待(在 "busy-loop" 中)直到操作准备就绪。因为构造函数从不 returns,甚至从未调用对 setVisible()
的调用。您永远不会给线程做任何其他工作的机会,包括绘制 UI.
此外,对于 Java Swing,有一个特殊线程执行对 UI 的所有更新。当您创建组件或更新它们显示的内容时,必须使用该线程完成该工作。 Swing 提供了 SwingWorker
class to schedule short tasks that manipulate the UI. You can learn more about concurrency in Swing by completing the Concurrency in Swing tutorial.
编辑:GUI 现在弹出(感谢 matt ),但是当我按下开始按钮时,程序完全冻结,我必须在 jGrasp 中结束它。
我遇到 java NIO 的问题,当我 运行 服务器代码时,我的 GUI 没有弹出。
代码如下:
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;
}
// process selected keys...
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// Remove key from set so we don'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("Server");
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gui.pack();
gui.setLocationRelativeTo(null);
gui.setResizable(false);
gui.setVisible(true);
}
}
我在这里做错了什么?
我按照 this 教程进行了简单的调试(Iterator<SelectionKey> iterator = readyKeys.iterator();
缺少 <SelectionKey>
部分),我编译了它,运行,然后......什么都没有。
这是我写的全部代码,我不明白我做错了什么。
你需要分开你的任务。一个是启动一个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();
// 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;
}
}
}
}
}
然后将其他任务设为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();
}
这样,在EDT上启动了gui,永不结束的服务器循环占用了主线程。另一个小变化。不要扩展JFrame,只是在startGui 方法中创建一个JFrame。这样 所有 gui 工作都在 EDT 上完成。
并不是说我也删除了您的异常处理,只是让方法抛出异常。这样你就会看到一个 StackTrace。
关于您的新问题,为什么 gui 会冻结。这是因为你在 EDT 上阻塞。您的 start()
方法永远不会退出。解决这个问题的最粗略的方法是。
public void actionPerformed(ActionEvent e) {
if(e.getSource()==start){
new Thread( ()->start();).start();
}
}
这样做的目的是启动一个新线程到 运行 您的服务器。注意最明显的问题,如果你点击启动不止一次,它会启动多个线程!
在这种情况下,可能没有必要使用非阻塞 I/O。相反,主线程可以继续执行 I/O(使用传统的阻塞调用),同时使用 SwingWorker
调度对 UI 的更新。这些更新将发生在 Swing 事件调度线程中,因此您甚至不必担心创建自己的线程。
非阻塞 IO 允许您在不阻塞的情况下检查通道是否准备好执行某些操作。如果没有 I/O 操作准备就绪,线程可以执行其他任务,稍后再回来查看。
但是在您的程序中,您编写的代码可以有效地等待(在 "busy-loop" 中)直到操作准备就绪。因为构造函数从不 returns,甚至从未调用对 setVisible()
的调用。您永远不会给线程做任何其他工作的机会,包括绘制 UI.
此外,对于 Java Swing,有一个特殊线程执行对 UI 的所有更新。当您创建组件或更新它们显示的内容时,必须使用该线程完成该工作。 Swing 提供了 SwingWorker
class to schedule short tasks that manipulate the UI. You can learn more about concurrency in Swing by completing the Concurrency in Swing tutorial.