swing與執行緒

handawei_5發表於2010-10-19

swing與執行緒:

      1.如果一個動作需要花費很長時間,在一個獨立的工作執行緒中坐這個事情,不要在事件分配執行緒中做。

                意思是可以在事件分配的執行緒中另起一個執行緒做這個事情。

      2.除了事件分配執行緒,不要再任何執行緒中接觸swing元件。

 

如果一個耗時的任務,在過程中要更新GUI上的進度顯示。但是由於這個耗時的工作執行緒不能接觸GUI,如何解決?

可以使用EventQueue類的invokeLater(),和invokeAndWait()中實行。

這兩個方法的區別是invokeLater()會立即返回,而invokeAndWait()會等待執行完之後返回。

e.g:下面的示範,如果接連按good按鈕程式不會出現異常,但是接連按bad按鈕會出現異常。

package v1ch14.SwingThreadTest;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;

/**
 * This program demonstrates that a thread that runs in parallel with the event dispatch thread can
 * cause errors in Swing components.
 * @version 1.23 2007-05-17
 * @author Cay Horstmann
 */
public class SwingThreadTest
{
   public static void main(String[] args)
   {
      EventQueue.invokeLater(new Runnable()
         {
            public void run()
            {
               SwingThreadFrame frame = new SwingThreadFrame();
               frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
               frame.setVisible(true);
            }
         });
   }
}

/**
 * This frame has two buttons to fill a combo box from a separate thread. The "Good" button uses the
 * event queue, the "Bad" button modifies the combo box directly.
 */
class SwingThreadFrame extends JFrame
{
   public SwingThreadFrame()
   {
      setTitle("SwingThreadTest");

      final JComboBox combo = new JComboBox();
      combo.insertItemAt(Integer.MAX_VALUE, 0);
      combo.setPrototypeDisplayValue("set...");
      combo.setSelectedIndex(0);

      JPanel panel = new JPanel();

      JButton goodButton = new JButton("Good");
      goodButton.addActionListener(new ActionListener()
         {
            public void actionPerformed(ActionEvent event)
            {
               new Thread(new GoodWorkerRunnable(combo)).start();
            }
         });
      panel.add(goodButton);
      JButton badButton = new JButton("Bad");
      badButton.addActionListener(new ActionListener()
         {
            public void actionPerformed(ActionEvent event)
            {
               new Thread(new BadWorkerRunnable(combo)).start();
            }
         });
      panel.add(badButton);

      panel.add(combo);
      add(panel);
      pack();
   }
}

/**
 * This runnable modifies a combo box by randomly adding and removing numbers. This can result in
 * errors because the combo box methods are not synchronized and both the worker thread and the
 * event dispatch thread access the combo box.
 */
class BadWorkerRunnable implements Runnable
{
   public BadWorkerRunnable(JComboBox aCombo)
   {
      combo = aCombo;
      generator = new Random();
   }

   public void run()
   {
      try
      {
         while (true)
         {
            int i = Math.abs(generator.nextInt());
            if (i % 2 == 0) combo.insertItemAt(i, 0);
            else if (combo.getItemCount() > 0) combo.removeItemAt(i % combo.getItemCount());
            Thread.sleep(1);
         }
      }
      catch (InterruptedException e)
      {
      }
   }

   private JComboBox combo;
   private Random generator;
}

/**
 * This runnable modifies a combo box by randomly adding and removing numbers. In order to ensure
 * that the combo box is not corrupted, the editing operations are forwarded to the event dispatch
 * thread.
 */
class GoodWorkerRunnable implements Runnable
{
   public GoodWorkerRunnable(JComboBox aCombo)
   {
      combo = aCombo;
      generator = new Random();
   }

   public void run()
   {
      try
      {
         while (true)
         {
            EventQueue.invokeLater(new Runnable()
               {
                  public void run()
                  {
                     int i = Math.abs(generator.nextInt());
                     if (i % 2 == 0) combo.insertItemAt(i, 0);
                     else if (combo.getItemCount() > 0) combo.removeItemAt(i
                           % combo.getItemCount());
                  }
               });
            Thread.sleep(1);
         }
      }
      catch (InterruptedException e)
      {
      }
   }

   private JComboBox combo;
   private Random generator;
}

 

在java6中,這樣的類已經寫進了類庫,就是swingWorker<T,V>類,T是工作器執行緒的返回值,V是想過程方法傳遞的引數。

 e.g:

 

package v1ch14.SwingWorkerTest;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.*;

import javax.swing.*;

/**
 * This program demonstrates a worker thread that runs a potentially time-consuming task.
 * @version 1.1 2007-05-18
 * @author Cay Horstmann
 */
public class SwingWorkerTest
{
   public static void main(String[] args) throws Exception
   {
      EventQueue.invokeLater(new Runnable()
         {
            public void run()
            {
               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
{
   public SwingWorkerFrame()
   {
      chooser = new JFileChooser();
      chooser.setCurrentDirectory(new File("."));

      textArea = new JTextArea();
      add(new JScrollPane(textArea));
      setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

      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(new ActionListener()
         {
            public void actionPerformed(ActionEvent event)
            {
               // show file chooser dialog
               int result = chooser.showOpenDialog(null);

               // if file selected, set it as icon of the label
               if (result == JFileChooser.APPROVE_OPTION)
               {
                  textArea.setText("");
                  openItem.setEnabled(false);
                  textReader = new TextReader(chooser.getSelectedFile());
                  textReader.execute();
                  cancelItem.setEnabled(true);
               }
            }
         });

      cancelItem = new JMenuItem("Cancel");
      menu.add(cancelItem);
      cancelItem.setEnabled(false);
      cancelItem.addActionListener(new ActionListener()
         {
            public void actionPerformed(ActionEvent event)
            {
               textReader.cancel(true);
            }
         });
   }

   private class ProgressData
   {
      public int number;
      public String line;
   }

   private class TextReader extends SwingWorker<StringBuilder, ProgressData>
   {
      public TextReader(File file)
      {
         this.file = file;
      }

      // the following method executes in the worker thread; it doesn't touch Swing components

      @Override
      public StringBuilder doInBackground() throws IOException, InterruptedException
      {
         int lineNumber = 0;
         Scanner in = new Scanner(new FileInputStream(file));
         while (in.hasNextLine())
         {
            String line = in.nextLine();
            lineNumber++;
            text.append(line);
            text.append("\n");
            ProgressData data = new ProgressData();
            data.number = lineNumber;
            data.line = line;
            publish(data);
            Thread.sleep(1); // to test cancellation; no need to do this in your programs
         }
         return text;
      }

      // the following methods execute in the event dispatch thread

      @Override
      public void process(List<ProgressData> data)
      {                                                        //事件執行緒也就是中間過程,這裡為什麼是list呢,因為publish方法的引數時可變陣列,而且由publish方法傳過來的資料可能不是同一個蘋率,所以當執行這個方法的時候可能publish已經執行了很多次,所以是list引數不是單個引數
         if (isCancelled()) return;
         StringBuilder b = new StringBuilder();
         statusLine.setText("" + data.get(data.size() - 1).number);
         for (ProgressData d : data)
         {
            b.append(d.line);
            b.append("\n");
         }
         textArea.append(b.toString());
      }

      @Override
      public void done()
      {
         try
         {
            StringBuilder result = get();//這裡得到的是doInBackground方法的返回的值,也就是工作器執行緒的返回值。
            textArea.setText(result.toString());     //可以操作GUI
            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);
      }

      private File file;
      private StringBuilder text = new StringBuilder();
   };

   private JFileChooser chooser;
   private JTextArea textArea;
   private JLabel statusLine;
   private JMenuItem openItem;
   private JMenuItem cancelItem;
   private SwingWorker<StringBuilder, ProgressData> textReader;

   public static final int DEFAULT_WIDTH = 450;
   public static final int DEFAULT_HEIGHT = 350;
}

 主要實現了上面的EventQueue類的invokeLater(),和invokeAndWait()的代替:

    doInBackground()方法來實現後臺的工作執行緒。

    process(List<V> data) 這一方法來處理分配執行緒中的中間進度資料。可在這裡實現對gui 的操作。

   void publish(v... data)傳遞中間資料到事件執行緒。在工作執行緒中可以呼叫它

  viod execute() 為工作器執行緒的執行預定這個工作器(啟動工作器執行緒)。

 SwingWorker.StateValue  getState得到這個工作器執行緒的狀態。

 

 

但是也有一些意外:

1。可以在任一個執行緒中新增或移除事件監聽器

2。有些Swing方法時執行緒安全的

     JTextComponent.setText

     JTextArea.insert

     JTextArea.append

     JTextArea.replaceRange

     JComponent.repaint

     JComponent.revalidate

  Swing的設計者認為除了事件分配執行緒之外,從任何其它執行緒訪問元件永遠都是不安全的。因此你需要在事件分配執行緒中構建使用者介面。

相關文章