Java MVC客户端-服务端计算器点击计算后GUI冻结问题求助
问题根因
- Swing GUI的所有事件回调都运行在*事件调度线程(EDT)*中,你直接在按钮点击的ActionListener里执行同步Socket IO、阻塞式读服务端响应的操作,EDT被占住无法处理界面绘制、用户交互等事件,直接出现冻结。
- 服务端Controller逻辑存在bug:客户端第一次建立连接时,服务端Controller会先读取一行请求,再把Socket交给Application线程处理,而Application的menu方法又会再次调用readLine()读取下一行请求,导致第一次点击计算的请求被服务端吞掉,客户端一直等不到响应、死循环阻塞,进一步加重了界面冻结问题。
- 客户端readFromServer的逻辑冗余,服务端返回的结果已经携带\0结束标记,不需要循环拼接内容,且readLine本身是阻塞方法,未读到数据会一直等待,不会继续执行后续逻辑。
修复步骤
1. 改造客户端计算逻辑,异步执行网络请求避免阻塞EDT
把Socket通信逻辑放到独立工作线程执行,拿到结果后再切回EDT更新UI,修改ClientController的calculate方法:
public void calculate() { // 从UI取数必须在EDT执行,先拿到参数 final String firstNum = Integer.toString(view.getFirstNumber()); final String secNum = Integer.toString(view.getSecondNumber()); // 开工作线程执行阻塞式网络IO new Thread(() -> { socketOut.println("1" + firstNum + "," + secNum); final int result = Integer.parseInt(readFromServer()); // 更新UI必须切回EDT执行 SwingUtilities.invokeLater(() -> { view.setTheSolution(result); }); }).start(); }
2. 简化客户端读服务端响应的逻辑
private String readFromServer() { try { String s = socketIn.readLine(); // 直接移除结束标记返回即可 return s.replace("\0", ""); } catch (IOException e) { System.out.println("Error in reading from server."); e.printStackTrace(); return "0"; } }
3. 修复服务端Controller逻辑,删除提前读请求的冗余代码
修改ServerController的communicate方法:
public void communicate() { while(true) { try { Socket socket = serverSocket.accept(); BufferedReader socketIn = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter socketOut = new PrintWriter(socket.getOutputStream(), true); // 删除下面四行冗余代码,请求读取交给Application线程处理 // String request = socketIn.readLine(); // String[] args = request.split(","); // int firstNum = Integer.parseInt(args[0]); // int secNum = Integer.parseInt(args[1]); Application app = new Application(calc, socketOut, socketIn); pool.execute(app); } catch (IOException e) { e.printStackTrace(); } } }
额外优化建议
- 给工作线程增加异常捕获逻辑,网络异常时弹出提示告知用户
- 点击计算按钮后可以先将按钮置灰,避免用户重复点击发送重复请求,拿到结果后再恢复按钮可用状态
- 增加输入校验逻辑,避免用户输入非数字内容触发
parseInt崩溃
内容的提问来源于stack exchange,提问作者Morello
相关产品推荐
相关产品推荐

