💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
**1. SwingWorker** 可以将很耗时的任务交给 SwingWorker类(Swing工作线程)来处理,它可以让后台任务的实现变得简单。而显示UI的任务交给`EventQueue.invokeLater`方法来处理。 * javax.swing.SwingWorker<T, V> 6 * `abstract T doInBackground()` 覆盖这一方法来执行后台的任务并返回这一工作的结果。不允许程序员自己调用。 * `void process(List<V> data)` 覆盖这一方法来处理事件分配线程中的中间进度数据。不允许程序员自己调用。 * `void publish(V... data)` 传递中间进度数据到事件分配线程。只能从`doInBackground`方法内部调用这一方法。 * `void execute()` 为工作器线程的执行预定这个工作器。 * `SwingWorker.StateValue getState()` 得到这个工作器线程的状态,值为PENDING、STARTED、DONE之一。 * 调用`doInBackground`方法来完成耗时的工作,不时地在工作线程器中调用`publish`来报告工作进度。`publish`方法使得`process`方法在事件分配线程中执行处理进度数据。当工作完成时,在事件分配线程中调用`done`方法来完成UI的更新。 * 每当要在工作线程中做一些工作时,构建一个新的工作器(每个工作器对象只能被使用一次)。然后调用`execute`方法。典型的方式是在事件分配线程中调用`execute`方法,但本例子没有这样的需求。 * 假定工作器产生某种类型的结果;因此,SwingWorker<T, V>实现了Future< T>接口,使得可以通过调用Future< T>的`get`方法获取结果。但是由于`get`方法会阻塞,直到结果可用为止,因此不要在调用`execute`方法之后调用它。只在已经知道工作完成时调用它,是最为明智的。典型的是:可以在`done`方法中调用`get`方法。(有时没有调用`get`方法,处理进度数据就是你所需要的)。 * 中间的进度数据,以及最终的结果可以是任意类型。SwingWorker类有3种类型作为类型参数。SwingWorker<T, V>产生类型为T的结果,以及类型为V的进度数据。 * 要取消正在进行的工作,可以调用Future接口的`cancel`方法。当工作被取消时,`get`方法抛出CancellationException异常。 <br/> **2. 案例演示** 在程序中实现加载文本文件的命令和取消加载过程的命令。应该用一个长的文件来测试这个程序,在gutenberg目录下的crsto10.txt文件为本次测试用的文件。该文件在一个单独的线程中加载。 <br/> 在读取文件的过程中,Open菜单项被禁用,Cancel菜单项为可用;读取完成后,Open菜单为可用,Cancel菜单被禁用,状态行文本置为Done。读取每一行后,状态条中的线性计数器被更新。 <br/> 这个例子展示了后台任务典型的UI活动: * 在每一个工作单位完成之后,更新UI来显示进度。 * 在整个工作完成之后,对UI做最后的更新。 ```java import java.awt.*; import java.io.*; import java.util.*; import java.util.List; import java.util.concurrent.*; import javax.swing.*; /** * 该程序演示了一个运行潜在耗时任务的工作线程。 * * @author Cay Horstmann * @version 1.11 2015-06-21 */ public class SwingWorkerTest { public static void main(String[] args) throws Exception { //在事件分配线程中显示UI EventQueue.invokeLater(() -> { JFrame frame = new SwingWorkerFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }); } } /** * This frame has a text area to show the contents of a text file, a menu to open a file and * cancel the opening process, and a status line to show the file loading progress. */ class SwingWorkerFrame extends JFrame { private JFileChooser chooser; private JTextArea textArea; private JLabel statusLine; private JMenuItem openItem; private JMenuItem cancelItem; private SwingWorker<StringBuilder, ProgressData> textReader; public static final int TEXT_ROWS = 20; public static final int TEXT_COLUMNS = 60; public SwingWorkerFrame() { chooser = new JFileChooser(); chooser.setCurrentDirectory(new File(".")); textArea = new JTextArea(TEXT_ROWS, TEXT_COLUMNS); add(new JScrollPane(textArea)); statusLine = new JLabel(" "); add(statusLine, BorderLayout.SOUTH); JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); JMenu menu = new JMenu("File"); menuBar.add(menu); openItem = new JMenuItem("Open"); menu.add(openItem); openItem.addActionListener(event -> { //显示文件选择器对话框 int result = chooser.showOpenDialog(null); //如果选择了文件,请将其设置为标签的图标 if (result == JFileChooser.APPROVE_OPTION) { textArea.setText(""); openItem.setEnabled(false); //禁用Open菜单项 textReader = new TextReader(chooser.getSelectedFile()); textReader.execute(); //为工作器线程的执行预定这个工作器 cancelItem.setEnabled(true); } }); cancelItem = new JMenuItem("Cancel"); menu.add(cancelItem); cancelItem.setEnabled(false); //调用cancel取消当前工作 cancelItem.addActionListener(event -> textReader.cancel(true)); pack(); //更新整个窗口 } /** * 记录文件当前的行号和当前的文件 */ private class ProgressData { public int number; //记录文件有多少行 public String line; //记录文件的每一行数据 } /** * 继续Swing工作线程类,SwingWorker,并重新一些相关的方法 */ private class TextReader extends SwingWorker<StringBuilder, ProgressData> { private File file; private StringBuilder text = new StringBuilder(); public TextReader(File file) { this.file = file; } /** * 方法在工作线程中执行; 它不涉及Swing组件。该方法不需要程序员调用。 * 将处理文件数据的耗时工作放在doInBackground方法中完成。 * 读取一个文件,每次读取一行,调用publish方法发布当前的行号和当前的文本。 */ @Override public StringBuilder doInBackground() throws IOException, InterruptedException { int lineNumber = 0; try (Scanner in = new Scanner(new FileInputStream(file), "UTF-8")) { while (in.hasNextLine()) { String line = in.nextLine(); lineNumber++; text.append(line).append("\n"); ProgressData data = new ProgressData(); data.number = lineNumber; data.line = line; publish(data); //传递数据,该数据会由下面的process方法接收 Thread.sleep(1); //这只是用来测试的,如果测试取消; 无需在程序中执行此操作 } } return text; } /** * 方法在事件分发线程中执行。接触Swing组件。 * 忽略最后一行行号之外的所有行号,然后,把所有的行拼接在一起用于文本区的更新。 */ @Override public void process(List<ProgressData> data) { if (isCancelled()) return; //如果检测到事件分配线程被取消,则什么也不做 StringBuilder b = new StringBuilder(); statusLine.setText("" + data.get(data.size() - 1).number); for (ProgressData d : data) b.append(d.line).append("\n"); textArea.append(b.toString()); } /** * 当工作完成时,调用done来完成UI的更新 */ @Override public void done() { try { StringBuilder result = get(); textArea.setText(result.toString()); statusLine.setText("Done"); } catch (InterruptedException ex) { } catch (CancellationException ex) { textArea.setText(""); statusLine.setText("Cancelled"); } catch (ExecutionException ex) { statusLine.setText("" + ex.getCause()); } cancelItem.setEnabled(false); openItem.setEnabled(true); } } } ```