第10章:线程与 SWT - 不要阻塞 UI

第10章:线程与 SWT - 不要阻塞 UI


作者:步子哥 (steper@foxmail.com)


10.1 SWT 的线程规则

为什么只能在主线程操作 UI?

// 错误示例:在后台线程操作 UI
new Thread(() -> {
    button.setText("后台线程修改");  // 错误!会抛出异常
}).start();

// 正确示例:在主线程操作 UI
display.asyncExec(() -> {
    button.setText("主线程修改");  // 正确!
});

费曼解释:为什么不能跨线程操作 UI?

UI 线程 = 舞台上的演员
- 只有一个演员在台上(UI 线程)
- 后台线程 = 后台工作人员
- 后台工作人员不能直接上台表演
- 必须通过"导演"(display.asyncExec)传递任务

类比:
UI 线程 = 餐厅前台
- 前台服务员(UI 线程)直接服务顾客
- 后台厨师(后台线程)不能直接服务顾客
- 必须通过"传菜员"(asyncExec)把菜传给前台

跨线程访问 UI 的后果

// 错误示例:在后台线程修改 UI
new Thread(() -> {
    try {
        Thread.sleep(1000);  // 模拟耗时操作
        button.setText("后台线程修改");  // 抛出异常!
    } catch (Exception e) {
        e.printStackTrace();
    }
}).start();

异常信息:

org.eclipse.swt.SWTException: Invalid thread access

费曼解释:为什么会抛出异常?

跨线程访问 UI = 越权
- UI 线程有"权限"操作 UI
- 后台线程没有"权限"
- 后台线程强行操作 UI = 越权
- 抛出异常 = 被保安抓住

类比:
跨线程访问 UI = 擅自进入后台
- 前台(UI 线程):顾客可以进入
- 后台:只有员工可以进入
- 顾客(后台线程)擅自进入后台 = 违规
- 被保安抓住 = 抛出异常

代码示例:线程规则演示

public class ThreadRuleExample {
    public static void main(String[] args) {
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setText("线程规则示例");
        shell.setLayout(new GridLayout(1, false));
        
        // 按钮面板
        Composite buttonPanel = new Composite(shell, SWT.NONE);
        buttonPanel.setLayout(new FillLayout(SWT.HORIZONTAL));
        
        // 错误按钮:后台线程直接修改 UI
        Button wrongButton = new Button(buttonPanel, SWT.PUSH);
        wrongButton.setText("错误:后台线程修改");
        wrongButton.addListener(SWT.Selection, event -> {
            new Thread(() -> {
                try {
                    Thread.sleep(1000);
                    // 错误!后台线程不能直接修改 UI
                    wrongButton.setText("后台线程修改");
                } catch (Exception e) {
                    e.printStackTrace();
                    display.asyncExec(() -> {
                        wrongButton.setText("错误:" + e.getMessage());
                    });
                }
            }).start();
        });
        
        // 正确按钮:通过 asyncExec 修改 UI
        Button correctButton = new Button(buttonPanel, SWT.PUSH);
        correctButton.setText("正确:asyncExec");
        correctButton.addListener(SWT.Selection, event -> {
            new Thread(() -> {
                try {
                    Thread.sleep(1000);
                    // 正确!通过 asyncExec 修改 UI
                    display.asyncExec(() -> {
                        correctButton.setText("主线程修改");
                    });
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        });
        
        // 文本框:显示日志
        Text logText = new Text(shell, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL);
        GridData logData = new GridData();
        logData.horizontalAlignment = GridData.FILL;
        logData.verticalAlignment = GridData.FILL;
        logData.grabExcessHorizontalSpace = true;
        logData.grabExcessVerticalSpace = true;
        logData.heightHint = 200;
        logText.setLayoutData(logData);
        
        // 添加日志的辅助方法
        shell.getDisplay().asyncExec(() -> {
            logText.append("主线程:" + Thread.currentThread().getName() + "\n");
        });
        
        shell.setBounds(100, 100, 600, 400);
        shell.open();
        
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        
        display.dispose();
    }
}

10.2 后台任务处理

创建工作线程

// 正确示例:后台线程处理任务,主线程更新 UI
Button button = new Button(shell, SWT.PUSH);
button.setText("开始任务");

button.addListener(SWT.Selection, event -> {
    button.setEnabled(false);  // 禁用按钮,防止重复点击
    
    new Thread(() -> {
        try {
            // 后台线程:执行耗时任务
            for (int i = 0; i <= 100; i++) {
                final int progress = i;
                
                // 主线程:更新 UI
                display.asyncExec(() -> {
                    progressBar.setSelection(progress);
                    statusLabel.setText("进度:" + progress + "%");
                });
                
                Thread.sleep(50);  // 模拟耗时操作
            }
            
            // 任务完成
            display.asyncExec(() -> {
                button.setEnabled(true);  // 启用按钮
                statusLabel.setText("任务完成!");
            });
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
});

费曼解释:后台任务处理的流程

后台任务处理 = 餐厅点餐
1. 顾客(用户)点菜(点击按钮)
2. 前台服务员(UI 线程)把菜单给后厨(后台线程)
3. 后厨(后台线程)做菜(执行任务)
4. 做好后,后厨把菜给传菜员(asyncExec)
5. 传菜员把菜端给顾客(更新 UI)
6. 顾客享用美食(任务完成)

类比:
后台任务处理 = 快递
1. 你(用户)在淘宝下单(点击按钮)
2. 快递员(后台线程)取件(执行任务)
3. 快递员把包裹送到驿站(asyncExec)
4. 你去驿站取件(更新 UI)
5. 你收到包裹(任务完成)

避免 UI 卡顿

// 错误示例:在主线程执行耗时任务,UI 卡顿
Button wrongButton = new Button(shell, SWT.PUSH);
wrongButton.setText("错误:主线程执行耗时任务");
wrongButton.addListener(SWT.Selection, event -> {
    // 错误!在主线程执行耗时任务,UI 卡顿
    try {
        for (int i = 0; i <= 100; i++) {
            Thread.sleep(50);  // 模拟耗时操作
            progressBar.setSelection(i);
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});

// 正确示例:在后台线程执行耗时任务,UI 流畅
Button correctButton = new Button(shell, SWT.PUSH);
correctButton.setText("正确:后台线程执行耗时任务");
correctButton.addListener(SWT.Selection, event -> {
    new Thread(() -> {
        // 正确!在后台线程执行耗时任务,UI 流畅
        try {
            for (int i = 0; i <= 100; i++) {
                final int progress = i;
                display.asyncExec(() -> {
                    progressBar.setSelection(progress);
                });
                Thread.sleep(50);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
});

费曼解释:为什么后台线程可以避免 UI 卡顿?

UI 卡顿 = 独木桥
- 只有一个人(主线程)在桥上
- 如果这个人走得慢(执行耗时任务)
- 后面的人(UI 事件)都过不去
- UI 卡顿 = 人群拥堵

后台线程 = 架设新桥
- 原来一座桥(主线程):UI 事件
- 新建一座桥(后台线程):耗时任务
- 两座桥互不干扰
- UI 流畅 = 人群畅通

类比:
UI 卡顿 = 一条车道
- 只有一条车道(主线程)
- 如果有一辆车开得很慢(耗时任务)
- 后面的车都超不过去
- 堵车 = UI 卡顿

后台线程 = 新建车道
- 新建一条车道(后台线程)
- 慢车走新车道
- 快车走旧车道
- 互不干扰,畅通无阻

代码示例:后台任务处理

public class BackgroundTaskExample {
    public static void main(String[] args) {
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setText("后台任务处理示例");
        shell.setLayout(new GridLayout(1, false));
        
        // 进度条
        ProgressBar progressBar = new ProgressBar(shell, SWT.SMOOTH);
        progressBar.setMinimum(0);
        progressBar.setMaximum(100);
        GridData progressData = new GridData();
        progressData.horizontalAlignment = GridData.FILL;
        progressData.grabExcessHorizontalSpace = true;
        progressBar.setLayoutData(progressData);
        
        // 状态标签
        Label statusLabel = new Label(shell, SWT.CENTER);
        statusLabel.setText("状态:就绪");
        
        // 按钮面板
        Composite buttonPanel = new Composite(shell, SWT.NONE);
        buttonPanel.setLayout(new FillLayout(SWT.HORIZONTAL));
        
        // 错误按钮
        Button wrongButton = new Button(buttonPanel, SWT.PUSH);
        wrongButton.setText("错误:主线程执行");
        wrongButton.addListener(SWT.Selection, event -> {
            wrongButton.setEnabled(false);
            
            // 错误!在主线程执行耗时任务,UI 卡顿
            try {
                for (int i = 0; i <= 100; i++) {
                    Thread.sleep(50);
                    progressBar.setSelection(i);
                    statusLabel.setText("进度:" + i + "%");
                }
                wrongButton.setEnabled(true);
                statusLabel.setText("任务完成!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        
        // 正确按钮
        Button correctButton = new Button(buttonPanel, SWT.PUSH);
        correctButton.setText("正确:后台线程执行");
        correctButton.addListener(SWT.Selection, event -> {
            correctButton.setEnabled(false);
            
            // 正确!在后台线程执行耗时任务,UI 流畅
            new Thread(() -> {
                try {
                    for (int i = 0; i <= 100; i++) {
                        final int progress = i;
                        
                        // 主线程:更新 UI
                        display.asyncExec(() -> {
                            progressBar.setSelection(progress);
                            statusLabel.setText("进度:" + progress + "%");
                        });
                        
                        Thread.sleep(50);
                    }
                    
                    // 任务完成
                    display.asyncExec(() -> {
                        correctButton.setEnabled(true);
                        statusLabel.setText("任务完成!");
                    });
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        });
        
        shell.setBounds(100, 100, 600, 200);
        shell.open();
        
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        
        display.dispose();
    }
}

10.3 从后台线程更新 UI

Display.asyncExec()

// asyncExec:异步执行(立即返回)
display.asyncExec(() -> {
    // 在主线程执行
    button.setText("更新");
});

System.out.println("asyncExec 立即返回");

费曼解释:asyncExec 的工作原理

asyncExec = 发送快递
- 你(后台线程)把快递(任务)给快递员(asyncExec)
- 快递员把快递送到前台(主线程)
- 你不用等快递员回来,可以继续做其他事
- 快递员到了前台,把快递交给前台服务员(执行任务)

类比:
asyncExec = 发送邮件
- 你写好邮件(任务)
- 点击"发送"(asyncExec)
- 邮件系统把邮件送到收件人(主线程)
- 你不用等收件人收到邮件,可以继续做其他事
- 收件人收到邮件,阅读邮件(执行任务)

Display.syncExec()

// syncExec:同步执行(等待完成)
display.syncExec(() -> {
    // 在主线程执行
    button.setText("更新");
});

System.out.println("syncExec 等待完成后才打印");

费曼解释:syncExec 的工作原理

syncExec = 送快递并等待回复
- 你(后台线程)把快递(任务)给快递员(syncExec)
- 快递员把快递送到前台(主线程)
- 快递员等待前台服务员处理完(执行任务)
- 快递员把回复给你
- 你收到回复后,继续做其他事

类比:
syncExec = 打电话
- 你(后台线程)给前台(主线程)打电话(syncExec)
- 前台接听电话,执行任务
- 你等待前台完成任务
- 前台完成任务,挂断电话
- 你收到回复,继续做其他事

两者的区别与选择

// asyncExec:异步执行,不阻塞后台线程
display.asyncExec(() -> {
    button.setText("asyncExec");
});
System.out.println("asyncExec 不阻塞");

// syncExec:同步执行,阻塞后台线程
display.syncExec(() -> {
    button.setText("syncExec");
});
System.out.println("syncExec 阻塞完成后才打印");

费曼解释:如何选择?

asyncExec:选择场景
- 只需要更新 UI,不需要返回值
- 不关心更新何时完成
- 推荐:大多数情况

syncExec:选择场景
- 需要等待 UI 更新完成
- 需要获取 UI 的返回值
- 谨慎使用:可能阻塞后台线程

类比:
asyncExec = 发送短信
- 发送短信
- 不等回复
- 继续做其他事

syncExec = 打电话
- 打电话
- 等对方接听并回复
- 收到回复后,继续做其他事

代码示例:asyncExec vs syncExec

public class AsyncSyncExample {
    public static void main(String[] args) {
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setText("asyncExec vs syncExec");
        shell.setLayout(new GridLayout(1, false));
        
        // 文本框:显示日志
        Text logText = new Text(shell, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL);
        GridData logData = new GridData();
        logData.horizontalAlignment = GridData.FILL;
        logData.verticalAlignment = GridData.FILL;
        logData.grabExcessHorizontalSpace = true;
        logData.grabExcessVerticalSpace = true;
        logData.heightHint = 200;
        logText.setLayoutData(logData);
        
        // 添加日志的辅助方法
        shell.getDisplay().asyncExec(() -> {
            logText.append("主线程:" + Thread.currentThread().getName() + "\n");
        });
        
        // 按钮:asyncExec
        Button asyncButton = new Button(shell, SWT.PUSH);
        asyncButton.setText("asyncExec");
        asyncButton.addListener(SWT.Selection, event -> {
            new Thread(() -> {
                logText.append("后台线程:" + Thread.currentThread().getName() + "\n");
                
                long startTime = System.currentTimeMillis();
                display.asyncExec(() -> {
                    logText.append("asyncExec:主线程:" + Thread.currentThread().getName() + "\n");
                });
                long endTime = System.currentTimeMillis();
                
                logText.append("asyncExec 耗时:" + (endTime - startTime) + "ms\n");
                logText.append("asyncExec 立即返回,不阻塞\n");
            }).start();
        });
        
        // 按钮:syncExec
        Button syncButton = new Button(shell, SWT.PUSH);
        syncButton.setText("syncExec");
        syncButton.addListener(SWT.Selection, event -> {
            new Thread(() -> {
                logText.append("后台线程:" + Thread.currentThread().getName() + "\n");
                
                long startTime = System.currentTimeMillis();
                display.syncExec(() -> {
                    logText.append("syncExec:主线程:" + Thread.currentThread().getName() + "\n");
                });
                long endTime = System.currentTimeMillis();
                
                logText.append("syncExec 耗时:" + (endTime - startTime) + "ms\n");
                logText.append("syncExec 等待完成后才返回\n");
            }).start();
        });
        
        shell.setBounds(100, 100, 600, 400);
        shell.open();
        
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        
        display.dispose();
    }
}

10.4 进度对话框(ProgressMonitorDialog)

后台任务的进度反馈

// 使用 ProgressMonitorDialog 显示进度
Button button = new Button(shell, SWT.PUSH);
button.setText("执行任务");

button.addListener(SWT.Selection, event -> {
    ProgressMonitorDialog dialog = new ProgressMonitorDialog(shell);
    
    try {
        dialog.run(true, true, monitor -> {
            monitor.beginTask("执行任务中...", 100);
            
            for (int i = 0; i <= 100; i++) {
                if (monitor.isCanceled()) {
                    throw new OperationCanceledException();
                }
                
                monitor.subTask("进度:" + i + "%");
                monitor.worked(1);
                
                Thread.sleep(50);  // 模拟耗时操作
            }
            
            monitor.done();
        });
    } catch (InvocationTargetException | InterruptedException e) {
        e.printStackTrace();
    } catch (OperationCanceledException e) {
        // 用户取消了任务
        System.out.println("任务被取消");
    }
});

费曼解释:ProgressMonitorDialog 的原理

ProgressMonitorDialog = 下载窗口
- 显示进度条
- 显示当前任务
- 用户可以取消
- 自动处理线程问题

类比:
ProgressMonitorDialog = 复印机
- 显示进度:第 10/100 页
- 显示当前任务:正在复印
- 用户可以取消:点击"停止"
- 自动处理:你不用管复印机内部怎么工作

取消操作的处理

// 处理取消操作
try {
    dialog.run(true, true, monitor -> {
        monitor.beginTask("执行任务中...", 100);
        
        for (int i = 0; i <= 100; i++) {
            if (monitor.isCanceled()) {
                throw new OperationCanceledException();
            }
            
            monitor.subTask("进度:" + i + "%");
            monitor.worked(1);
            
            Thread.sleep(50);
        }
        
        monitor.done();
    });
} catch (OperationCanceledException e) {
    System.out.println("用户取消了任务");
}

费曼解释:取消操作的原理

取消操作 = 拔掉插头
- 用户点击"取消"(拔插头)
- monitor.isCanceled() 返回 true(检测到没电)
- 抛出 OperationCanceledException(停止任务)
- 对话框关闭(任务结束)

类比:
取消操作 = 拔掉电脑插头
- 你点击"取消"(拔插头)
- 程序检测到断电(isCanceled)
- 程序停止(抛出异常)
- 对话框关闭(任务结束)

代码示例:ProgressMonitorDialog

public class ProgressMonitorDialogExample {
    public static void main(String[] args) {
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setText("ProgressMonitorDialog 示例");
        shell.setLayout(new GridLayout(1, false));
        
        // 按钮:执行任务
        Button runButton = new Button(shell, SWT.PUSH);
        runButton.setText("执行任务");
        runButton.addListener(SWT.Selection, event -> {
            ProgressMonitorDialog dialog = new ProgressMonitorDialog(shell);
            
            try {
                dialog.run(true, true, monitor -> {
                    monitor.beginTask("执行任务中...", 100);
                    
                    for (int i = 0; i <= 100; i++) {
                        if (monitor.isCanceled()) {
                            throw new OperationCanceledException();
                        }
                        
                        monitor.subTask("进度:" + i + "%");
                        monitor.worked(1);
                        
                        Thread.sleep(50);  // 模拟耗时操作
                    }
                    
                    monitor.done();
                    
                    // 任务完成
                    display.asyncExec(() -> {
                        MessageBox messageBox = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
                        messageBox.setText("提示");
                        messageBox.setMessage("任务完成!");
                        messageBox.open();
                    });
                });
            } catch (InvocationTargetException | InterruptedException e) {
                e.printStackTrace();
            } catch (OperationCanceledException e) {
                // 用户取消了任务
                display.asyncExec(() -> {
                    MessageBox messageBox = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
                    messageBox.setText("提示");
                    messageBox.setMessage("任务被取消!");
                    messageBox.open();
                });
            }
        });
        
        shell.setBounds(100, 100, 400, 100);
        shell.open();
        
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        
        display.dispose();
    }
}

10.5 实战案例:文件复制进度条

public class FileCopyExample {
    public static void main(String[] args) {
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setText("文件复制进度条");
        shell.setLayout(new GridLayout(1, false));
        
        // 源文件
        Label sourceLabel = new Label(shell, SWT.NONE);
        sourceLabel.setText("源文件:");
        
        Text sourceText = new Text(shell, SWT.BORDER);
        sourceText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        
        Button sourceButton = new Button(shell, SWT.PUSH);
        sourceButton.setText("选择源文件");
        sourceButton.addListener(SWT.Selection, event -> {
            FileDialog fileDialog = new FileDialog(shell, SWT.OPEN);
            fileDialog.setText("选择源文件");
            String file = fileDialog.open();
            if (file != null) {
                sourceText.setText(file);
            }
        });
        
        // 目标文件
        Label targetLabel = new Label(shell, SWT.NONE);
        targetLabel.setText("目标文件:");
        
        Text targetText = new Text(shell, SWT.BORDER);
        targetText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        
        Button targetButton = new Button(shell, SWT.PUSH);
        targetButton.setText("选择目标文件");
        targetButton.addListener(SWT.Selection, event -> {
            FileDialog fileDialog = new FileDialog(shell, SWT.SAVE);
            fileDialog.setText("选择目标文件");
            String file = fileDialog.open();
            if (file != null) {
                targetText.setText(file);
            }
        });
        
        // 进度条
        ProgressBar progressBar = new ProgressBar(shell, SWT.SMOOTH);
        progressBar.setMinimum(0);
        progressBar.setMaximum(100);
        progressBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        
        // 状态标签
        Label statusLabel = new Label(shell, SWT.CENTER);
        statusLabel.setText("状态:就绪");
        
        // 复制按钮
        Button copyButton = new Button(shell, SWT.PUSH);
        copyButton.setText("开始复制");
        copyButton.addListener(SWT.Selection, event -> {
            String sourceFile = sourceText.getText();
            String targetFile = targetText.getText();
            
            if (sourceFile.isEmpty() || targetFile.isEmpty()) {
                MessageBox messageBox = new MessageBox(shell, SWT.ICON_WARNING | SWT.OK);
                messageBox.setText("警告");
                messageBox.setMessage("请选择源文件和目标文件!");
                messageBox.open();
                return;
            }
            
            copyButton.setEnabled(false);
            
            new Thread(() -> {
                try {
                    // 复制文件
                    copyFileWithProgress(sourceFile, targetFile, progressBar, statusLabel);
                    
                    // 复制完成
                    display.asyncExec(() -> {
                        copyButton.setEnabled(true);
                        statusLabel.setText("复制完成!");
                        MessageBox messageBox = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
                        messageBox.setText("提示");
                        messageBox.setMessage("文件复制完成!");
                        messageBox.open();
                    });
                } catch (Exception e) {
                    e.printStackTrace();
                    
                    display.asyncExec(() -> {
                        copyButton.setEnabled(true);
                        statusLabel.setText("复制失败:" + e.getMessage());
                    });
                }
            }).start();
        });
        
        shell.setBounds(100, 100, 500, 300);
        shell.open();
        
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        
        display.dispose();
    }
    
    private static void copyFileWithProgress(String source, String target, 
            ProgressBar progressBar, Label statusLabel) throws IOException {
        Path sourcePath = Paths.get(source);
        Path targetPath = Paths.get(target);
        
        long fileSize = Files.size(sourcePath);
        long copiedBytes = 0;
        
        try (InputStream in = Files.newInputStream(sourcePath);
             OutputStream out = Files.newOutputStream(targetPath)) {
            byte[] buffer = new byte[8192];
            int bytesRead;
            
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
                copiedBytes += bytesRead;
                
                // 更新进度
                final int progress = (int)(copiedBytes * 100 / fileSize);
                display.asyncExec(() -> {
                    progressBar.setSelection(progress);
                    statusLabel.setText("复制进度:" + progress + "%");
                });
            }
        }
    }
}

费曼解释:文件复制的进度反馈

文件复制 = 搬家
- 源文件 = 旧家
- 目标文件 = 新家
- 文件内容 = 家具
- 复制 = 搬运家具
- 进度条 = 搬了多少
- 状态标签 = 搬到哪了

类比:
文件复制 = 餐厅上菜
- 源文件 = 厨房
- 目标文件 = 餐桌
- 文件内容 = 菜品
- 复制 = 上菜
- 进度条 = 上了多少
- 状态标签 = 上了什么菜

10.6 本章小结

线程与 SWT 总结

概念作用类比
主线程操作 UI舞台演员
后台线程执行耗时任务后台工作人员
asyncExec异步更新 UI发送快递
syncExec同步更新 UI打电话
ProgressMonitorDialog显示进度下载窗口

费曼测试:你能解释清楚吗?

  1. 为什么不能跨线程操作 UI?

- UI 线程有"权限",后台线程没有,强行操作会抛出异常

  1. asyncExec 和 syncExec 的区别?

- asyncExec:异步执行,立即返回 - syncExec:同步执行,等待完成

  1. 如何避免 UI 卡顿?

- 在后台线程执行耗时任务,主线程只负责更新 UI

  1. ProgressMonitorDialog 的用途?

- 显示后台任务的进度,支持取消操作

下一章预告

现在你已经掌握了线程与 SWT,
UI 始终流畅响应,
但还不知道如何正确管理 SWT 资源。

下一章,我们将学习资源管理,
避免内存泄漏,
让应用长期稳定运行。

练习题:

  1. 创建一个后台任务,计算斐波那契数列,通过 asyncExec 更新 UI。
  2. 使用 ProgressMonitorDialog 显示后台任务的进度,支持取消操作。
  3. 创建一个文件复制程序,显示复制进度。

(提示:斐波那契数列计算放在后台线程,通过 asyncExec 更新 UI)

← 返回目录