递归复制文件时SwingWorker进度条不更新问题求助
SwingWorker递归文件复制进度条不实时更新的解决方法
核心问题分析
- EDT被阻塞:按钮点击事件中的
while (!worker.isDone())循环运行在事件调度线程(EDT)中,完全占用UI线程,导致所有UI更新操作被挂起,直到复制完成才会一次性刷新。 - 后台线程直接操作UI:
copyFiles方法里直接调用label.setText修改UI组件,违反Swing单线程模型——所有UI操作必须在EDT执行,否则会导致UI异常或不更新。 - 进度发布时机错误:仅在所有文件复制完成后调用一次
publish(copiedFiles),没有在每次复制单个文件后发布进度,process方法无法实时接收进度数据。 - 线程安全隐患:
totalFiles和copiedFiles作为类级共享变量,多线程访问时没有同步机制,可能导致计数不准确。
修复方案及代码实现
针对上述问题,修正后的代码如下:
package swingworkertest; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JProgressBar; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.StandardCopyOption; import java.util.List; import javax.swing.BoxLayout; import javax.swing.SwingWorker; public class App extends JFrame { private JPanel mainPanel; private JButton button; private JLabel label; private JProgressBar progress; private int totalFiles = 0; // 递归统计文件总数 private void getTotalFiles(File src) { File[] files = src.listFiles(); if (files == null) return; for (File file : files) { if (file.isDirectory()) { getTotalFiles(file); } else { totalFiles++; } } } // 递归复制文件,每次复制后通知进度 private void copyFiles(File src, File dst, SwingWorker<?, Integer> worker) throws IOException { File[] files = src.listFiles(); if (files == null) return; for (File file : files) { File target = new File(dst, file.getName()); if (file.isDirectory()) { if (!target.exists()) { target.mkdirs(); } copyFiles(file, target, worker); } else { Files.copy(file.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); // 每次复制完成后发布当前已复制数量 worker.publish(1); } } } public App(String title) { this.setTitle(title); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); String srcPath = "C:\\Users\\Vinny\\Desktop\\workingDirectory"; String dstPath = "C:\\Users\\Vinny\\Desktop\\copyToHere"; File src = new File(srcPath); File dst = new File(dstPath); mainPanel = new JPanel(); mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); button = new JButton("Copy"); label = new JLabel("Files Copied: 0/0"); progress = new JProgressBar(); progress.setStringPainted(true); button.addActionListener((e) -> { button.setEnabled(false); // 防止重复点击 totalFiles = 0; getTotalFiles(src); label.setText("Files Copied: 0/" + totalFiles); progress.setMaximum(totalFiles); SwingWorker<Boolean, Integer> worker = new SwingWorker<>() { private int copiedFiles = 0; @Override protected Boolean doInBackground() throws Exception { copyFiles(src, dst, this); return true; } @Override protected void process(List<Integer> chunks) { // 累加每次发布的进度增量 copiedFiles += chunks.size(); // 更新标签和进度条(process方法运行在EDT) label.setText("Files Copied: " + copiedFiles + "/" + totalFiles); progress.setValue(copiedFiles); } @Override protected void done() { button.setEnabled(true); System.out.println("Files Copied"); } }; worker.execute(); }); mainPanel.add(button); mainPanel.add(label); mainPanel.add(progress); this.add(mainPanel); this.pack(); this.setVisible(true); } public static void main(String[] args) { // 在EDT启动应用 javax.swing.SwingUtilities.invokeLater(() -> new App("File Copy")); } }
关键修复点说明
- 移除阻塞EDT的循环:删除按钮事件中的死循环,让EDT能够正常处理UI刷新请求。
- 通过publish/process更新UI:每次复制单个文件后调用
worker.publish(1),在process方法中累加进度并更新UI,确保所有UI操作在EDT执行。 - 避免共享变量冲突:将
copiedFiles改为SwingWorker内部的变量,避免多线程共享带来的线程安全问题。 - 优化文件操作:使用
File.listFiles()减少重复创建File对象,添加StandardCopyOption.REPLACE_EXISTING处理文件覆盖场景,同时添加按钮禁用逻辑防止重复触发复制。
内容的提问来源于stack exchange,提问作者Vince
相关产品推荐
相关产品推荐

