为什么要使用SwingWorker
在swing开发中,如果一个应用程序,执行一些任务,需要大量的时间来完成,比如下载一个大文件或执行一个复杂的数据库查询。
我们假设这些任务是由用户使用一个按钮触发的。在单线程应用程序,用户单击按钮,进入计算的过程,然后等待任务完成之前,所有的事件都在主线程EDT线程进行。
但如果某些任务耗时很长,用户将甚至不能在中途取消任务,应用程序必须响应只有当长任务完成。不幸的是,许多应用程序显着这样的行为和用户感到沮丧,程序仿佛卡死一样。
多线程可以解决这个问题。它使应用程序能够在不同的线程上执行长任务,但多线程带来一个问题,如果需要实时和主线程EDT进行数据交换,该怎么办?我们知道所有的Swing对象都只有一个线程处理,EDT事件调度线程,这导致一个问题:我们不能在EDT事件分派线程以外的其他线程共享对象的对象。
SwingWorker
SwingWorker是一个抽象类,java将它包装好,供方便调用,下面的例子使用字符串对象来通知应用程序。
提供了两个泛型参数。第一个代表返回的对象类型。另一个代表了通知(更新)应用程序的信息的类型,并在下面的例子中高亮显示。
public class MyBlankWorker extends SwingWorker<Integer, String> { @Override protected Integer doInBackground() throws Exception { // Start publish("Start"); setProgress(1); // More work was done publish("More work was done"); setProgress(10); // Complete publish("Complete"); setProgress(100); return 1; } @Override protected void process(List< String> chunks) { // Messages received from the doInBackground() (when invoking the publish() method) } }
通过setprogress() 设置0和100之间的整数。doinbackground() 用于漫长任务执行。此方法不是由事件调度线程调用的,而是由另一个线程(称为工作线程)。我们可以用publish()方法和/或setprogress()更新进度。调用两个方法都会对事件调度线程的创建新任务,它是工作线程和事件调度线程的线程之间的单向桥。
doinbackground()中调用publish()方法和/或setprogress()必须防止大量的任务发送给事件调度线程,引发洪水事件。
注意:publish()从工作线程调用,而process()由事件调度线程EDT调用。
举例:
输入:
publish("a"); publish("b", "c"); publish("d", "e", "f");
结果就是:
process("a", "b", "c", "d", "e", "f")
最后是一个文件全文检索的异步查询例子,查询某目录下所有的txt文件
import java.io.File; import java.util.ArrayList; import java.util.List; import javax.swing.JTextArea; import javax.swing.SwingWorker; import org.apache.commons.io.FileUtils; import org.apache.commons.io.filefilter.SuffixFileFilter; import org.apache.commons.io.filefilter.TrueFileFilter; import org.apache.commons.lang.StringUtils; /** * Searches the text files under the given directory and counts the number of instances a given word is found in these * file. * * @author Albert Attard */ public class SearchForWordWorker extends SwingWorker<Integer, String> { private static void failIfInterrupted() throws InterruptedException { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException("Interrupted while searching files"); } } /** The word that is searched */ private final String word; /** The directory under which the search occurs. All text files found under the given directory are searched. */ private final File directory; /** The text area where messages are written. */ private final JTextArea messagesTextArea; /** * Creates an instance of the worker * * @param word * The word to search * @param directory * the directory under which the search will occur. All text files found under the given directory are * searched * @param messagesTextArea * The text area where messages are written */ public SearchForWordWorker(final String word, final File directory, final JTextArea messagesTextArea) { this.word = word; this.directory = directory; this.messagesTextArea = messagesTextArea; } @Override protected Integer doInBackground() throws Exception { // The number of instances the word is found int matches = 0; /* * List all text files under the given directory using the Apache IO library. This process cannot be interrupted * (stopped through cancellation). That is why we are checking right after the process whether it was interrupted or * not. */ publish("Listing all text files under the directory: " + directory); final List<File> textFiles = new ArrayList<>(FileUtils.listFiles(directory, new SuffixFileFilter(".txt"), TrueFileFilter.TRUE)); SearchForWordWorker.failIfInterrupted(); publish("Found " + textFiles.size() + " text files under the directory: " + directory); for (int i = 0, size = textFiles.size(); i < size; i++) { /* * In order to respond to the cancellations, we need to check whether this thread (the worker thread) was * interrupted or not. If the thread was interrupted, then we simply throw an InterruptedException to indicate * that the worker thread was cancelled. */ SearchForWordWorker.failIfInterrupted(); // Update the status and indicate which file is being searched. final File file = textFiles.get(i); publish("Searching file: " + file); /* * Read the file content into a string, and count the matches using the Apache common IO and Lang libraries * respectively. */ final String text = FileUtils.readFileToString(file); matches += StringUtils.countMatches(text, word); Thread.sleep(20); // Update the progress setProgress((i + 1) * 100 / size); } // Return the number of matches found return matches; } @Override protected void process(final List<String> chunks) { // Updates the messages text area for (final String string : chunks) { messagesTextArea.append(string); messagesTextArea.append("\n"); } } }
JFrame UI类如下
package com.javacreed.examples.swing.worker.part3; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JProgressBar; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.SwingWorker.StateValue; public class Application extends JFrame { /** */ private static final long serialVersionUID = -8668818312732181049L; private Action searchCancelAction; private Action browseAction; private JTextField wordTextField; private JTextField directoryPathTextField; private JTextArea messagesTextArea; private JProgressBar searchProgressBar; private SearchForWordWorker searchWorker; public Application() { initActions(); initComponents(); } private void cancel() { searchWorker.cancel(true); } private void initActions() { browseAction = new AbstractAction("Browse") { private static final long serialVersionUID = 4669650683189592364L; @Override public void actionPerformed(final ActionEvent e) { final File dir = new File(directoryPathTextField.getText()).getAbsoluteFile(); final JFileChooser fileChooser = new JFileChooser(dir.getParentFile()); fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); final int option = fileChooser.showOpenDialog(Application.this); if (option == JFileChooser.APPROVE_OPTION) { final File selected = fileChooser.getSelectedFile(); directoryPathTextField.setText(selected.getAbsolutePath()); } } }; searchCancelAction = new AbstractAction("Search") { private static final long serialVersionUID = 4669650683189592364L; @Override public void actionPerformed(final ActionEvent e) { if (searchWorker == null) { search(); } else { cancel(); } } }; } private void initComponents() { setLayout(new GridBagLayout()); GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = 0; constraints.insets = new Insets(2, 2, 2, 2); add(new JLabel("Word: "), constraints); wordTextField = new JTextField(); wordTextField.setText("Hello"); constraints = new GridBagConstraints(); constraints.gridx = 1; constraints.gridy = 0; constraints.gridwidth = 2; constraints.insets = new Insets(2, 2, 2, 2); constraints.weightx = 1; constraints.fill = GridBagConstraints.BOTH; add(wordTextField, constraints); constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = 1; constraints.insets = new Insets(2, 2, 2, 2); add(new JLabel("Path: "), constraints); directoryPathTextField = new JTextField(); directoryPathTextField.setText("C:\\Users\\Albert\\Work\\JavaCreed\\examples"); constraints = new GridBagConstraints(); constraints.gridx = 1; constraints.gridy = 1; constraints.gridwidth = 1; constraints.insets = new Insets(2, 2, 2, 2); constraints.weightx = 1; constraints.fill = GridBagConstraints.BOTH; add(directoryPathTextField, constraints); constraints = new GridBagConstraints(); constraints.gridx = 2; constraints.gridy = 1; constraints.insets = new Insets(2, 2, 2, 2); add(new JButton(browseAction), constraints); messagesTextArea = new JTextArea(); messagesTextArea.setEditable(false); constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = 2; constraints.gridwidth = 3; constraints.insets = new Insets(2, 2, 2, 2); constraints.weightx = 1; constraints.weighty = 1; constraints.fill = GridBagConstraints.BOTH; add(new JScrollPane(messagesTextArea), constraints); searchProgressBar = new JProgressBar(); searchProgressBar.setStringPainted(true); searchProgressBar.setVisible(false); constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = 3; constraints.gridwidth = 2; constraints.insets = new Insets(2, 2, 2, 2); constraints.weightx = 1; constraints.fill = GridBagConstraints.BOTH; add(searchProgressBar, constraints); constraints = new GridBagConstraints(); constraints.gridx = 2; constraints.gridy = 3; constraints.insets = new Insets(2, 2, 2, 2); constraints.weightx = 0; add(new JButton(searchCancelAction), constraints); } private void search() { final String word = wordTextField.getText(); final File directory = new File(directoryPathTextField.getText()); messagesTextArea.setText("Searching for word '" + word + "' in text files under: " + directory.getAbsolutePath() + "\n"); searchWorker = new SearchForWordWorker(word, directory, messagesTextArea); searchWorker.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(final PropertyChangeEvent event) { switch (event.getPropertyName()) { case "progress": searchProgressBar.setIndeterminate(false); searchProgressBar.setValue((Integer) event.getNewValue()); break; case "state": switch ((StateValue) event.getNewValue()) { case DONE: searchProgressBar.setVisible(false); searchCancelAction.putValue(Action.NAME, "Search"); searchWorker = null; break; case STARTED: case PENDING: searchCancelAction.putValue(Action.NAME, "Cancel"); searchProgressBar.setVisible(true); searchProgressBar.setIndeterminate(true); break; } break; } } }); searchWorker.execute(); } }
入口:
import javax.swing.JFrame; import javax.swing.SwingUtilities; public class Main { public static void main(final String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { final Application frame = new Application(); frame.setTitle("Swing Worker Demo"); frame.setSize(600, 400); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } }
引用:http://www.javacreed.com/swing-worker-example/