【问题标题】:Java AWT ThreadsJava AWT 线程
【发布时间】:2012-07-20 04:04:14
【问题描述】:

我在使用 netbeans Swing GUI 时遇到了线程问题。这是我第一次真正尝试使用 Java 的文件系统通知程序为备份程序开发 GUI。我有两个文件SyncUI.javaSync.java

几乎我想要发生的事情是您在jTextField1 文本字段中输入一个目录路径,该路径创建一个同步线程,该线程创建一个新的同步对象,然后在该对象上调用processEvents。当该目录中的文件发生更改时,我想将有关更改的文本添加到列表中。

在其当前状态下,UI 不再响应,但是 processEvents 没有在我的列表中添加任何内容。知道问题是什么吗?此外,由于我刚刚开始使用 java,因此欢迎任何建设性的批评。

SyncUI.java:

package sync;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.DefaultListModel;
import javax.swing.JList;
import javax.swing.SwingUtilities;

public class SyncUI extends javax.swing.JFrame {

    public SyncUI() {
        initComponents();
    }

    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {
        jButton1 = new javax.swing.JButton();
        jTextField1 = new javax.swing.JTextField();
        list1 = new java.awt.List();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jButton1.setText("Start");
        jButton1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton1ActionPerformed(evt);
            }
        });

        jTextField1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jTextField1ActionPerformed(evt);
            }
        });

        list1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                list1ActionPerformed(evt);
            }
        });

        org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    layout.setHorizontalGroup(
        layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
        .add(layout.createSequentialGroup()
        .addContainerGap()
        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
        .add(list1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        .add(layout.createSequentialGroup()
        .add(jTextField1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 329, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
        .add(jButton1)
        .add(0, 0, Short.MAX_VALUE)))
        .addContainerGap())
    );
    layout.setVerticalGroup(
        layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
        .add(layout.createSequentialGroup()
        .addContainerGap()
        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
        .add(jButton1)
        .add(jTextField1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
        .add(list1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 229, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
        .addContainerGap(25, Short.MAX_VALUE))
    );

        pack();
    }// </editor-fold>                        

private void jTextField1ActionPerformed(java.awt.event.ActionEvent evt) {                                            
    jButton1.doClick();
}                                           

private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {  
        //I tried to use invokeLater, this solved the problem of the UI nonresponse issue                                       
        SwingUtilities.invokeLater(new Runnable() {
            public void run()
            {
                //Creates a path dir that my Sync constructor needs to start file   notification watcher on that directory
                Path dir = Paths.get(jTextField1.getText());
                try
                {
                    //Creates a new sync object passing it the directory and the list in my GUI
                    Sync sync = new Sync(dir, list1);
                    try
                    {
                        sync.processEvents();
                    }
                    catch (InterruptedException ex)
                    {
                        Logger.getLogger(SyncUI.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                catch (IOException ex)
                {
                    Logger.getLogger(SyncUI.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

        });
}                                        

private void list1ActionPerformed(java.awt.event.ActionEvent evt) {                                      
    // TODO add your handling code here:
}                                     

public static void main(String args[]) throws IOException
{
    java.awt.EventQueue.invokeLater(new Runnable()
    {
        public void run()
        {
            SyncUI s = new SyncUI();
            s.setVisible(true);               
        }
    });     
}

// Variables declaration - do not modify                     
private javax.swing.JButton jButton1;
private javax.swing.JTextField jTextField1;
private java.awt.List list1;
// End of variables declaration                   
}

Sync.java:

package sync;

import static java.nio.file.StandardWatchEventKinds.*;
import java.nio.file.attribute.*;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;

public class Sync
{
    private final WatchService ws;
    private final Map<WatchKey,Path> keys;
    public java.awt.List list;

    public Sync(Path dir, java.awt.List list) throws IOException, InterruptedException
    {
        this.ws = FileSystems.getDefault().newWatchService();
        this.keys = new HashMap<WatchKey,Path>();
        this.list = list;
        recSet(dir);
        //this.processEvents();
    }

    private void register(Path dir) throws IOException
    {
        WatchKey key = dir.register(ws, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        keys.put(key, dir);
    }

    private void recSet(Path start) throws IOException
    {
        Files.walkFileTree(start, new SimpleFileVisitor<Path>()
        {     
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException
            {
                if(!Files.isHidden(dir))
                {
                    register(dir);
                    System.out.println(dir);
                }
            return FileVisitResult.CONTINUE;
            }
        });
    }

    void processEvents() throws IOException, InterruptedException
    {
        System.out.println("Entered processEvents");
        SwingUtilities.invokeLater(new Runnable() {
            public void run()
            {
                System.out.println("Entered run");
                list.add("test2");
                list.repaint();
                while(true)
                { 
                    WatchKey key;           
                    try
                    {
                        key = ws.take();
                    }
                    catch (InterruptedException x)
                    {
                        return;
                    }

                    Path dir = keys.get(key);
                    if (dir == null)
                    {
                        System.err.println("WatchKey not recognized");
                        continue;
                    }

                    for (WatchEvent<?> event: key.pollEvents())
                    {
                        WatchEvent.Kind<?> kind = event.kind();
                        WatchEvent<Path> ev = (WatchEvent<Path>)event;
                        Path filename = ev.context();
                        String name = dir.resolve(filename).toString();             

                        if (kind == OVERFLOW)
                            continue;               

                        if(kind == ENTRY_CREATE)
                        {
                            System.out.print("Entry Created: ");
                            File f = new File(name);

                            if(f.isDirectory())
                                try {
                                    register(dir.resolve(filename));
                                } catch (IOException ex) {
                                    Logger.getLogger(Sync.class.getName()).log(Level.SEVERE, null, ex);
                                }

                            System.out.println(name);
                            list.add(name);     
                        }
                        else if(kind == ENTRY_DELETE)
                        {
                            System.out.print("Entry Deleted: ");
                            System.out.println(name);                          
                        }
                        else if(kind == ENTRY_MODIFY)
                        {
                            File f = new File(name);
                            if(!f.isDirectory())
                        {
                            System.out.print("Entry Modify: ");
                            System.out.println(name);
                        }
                    }

                    boolean valid = key.reset();

                    if (!valid)
                        break;
                    }
                }
            }
        });       
    }
}

【问题讨论】:

  • 您可以在按钮按下时动态添加列表项吗?如果不是,这与 NIO 无关,您应该发布它。为了尽快获得更好的帮助,请发帖SSCCE
  • 是的,我能够做到 list.add("stuff");在我的 Button Press Action Listener 中,它确实将我的条目附加到列表中。此外,如果我在 processEvents 中的 while(true) 循环之前返回,它将附加到列表中。
  • 请注意,代码审查更适合进行建设性的批评,但请注意有关 `jTextField1 = new javax.swing.JTextField();` 1) 给它起一个合理的名称,例如 dirPathdirPathTextField 2) 提供JFileChooser 以选择目录。
  • 哦,对了。错过了while(true) 位。好的.. 不要阻塞 EDT(事件调度线程)——当这种情况发生时,GUI 将“冻结”。使用SwingWorker 处理长时间运行的任务。有关详细信息,请参阅Concurrency in Swing
  • 是的,我知道我需要进行这些更改。 gui 不像它只是我拼凑起来的一个基本示例,试图让核心功能正常工作。

标签: java multithreading swing awt jlist


【解决方案1】:

Swing 不是线程安全的,因此,如果您尝试在同一线程中更新 UI,您将遇到“应用程序冻结”行为。为了解决这个问题,您需要将更新 UI 的过程委托给另一个线程。这是使用 SwingUtilities.invokeLater(Java 5 和更早版本)方法和/或 SwingWorker 类(自 Java 6 起)实现的。

一些链接:

谷歌搜索: https://www.google.com.br/search?q=swing+thread+safe

【讨论】:

  • 我的用户界面没有冻结。确实如此,我查找了这些东西并放入: SwingUtilities.invokeLater(new Runnable() { public void run() { .... 这样 UI 线程不再被阻塞。问题是现在列表将不更新。
  • GregoryBillings,看看@MadProgrammer 的回答;)
【解决方案2】:

支持 davidbuzatto:

不,我认为你错过了 InvokeLater 所做的事情。 InvokeLater 确保可运行对象在 ETD 上执行。所以基本上,据我所知,您已经将长时间运行的事件阻止代码放回ETD。仅当您想要更新 UI 时使用 InvokeLater,当您想要实际进行处理时使用 Threads 或 SwingWorker

void processEvents() throws IOException, InterruptedException
    {
        System.out.println("Entered processEvents");

        // PLEASE ETD, PUT THIS AT THE END OF THE QUEUE AND EXECUTE
        // SO I RUN WITHIN YOUR CONTEXT
        SwingUtilities.invokeLater(new Runnable() {
            public void run()
            {

                // NOW RUNNING BACK ON THE ETD
                System.out.println("Entered run");
                list.add("test2");
                list.repaint();

                // NOW BLOCK THE ETD, SO NO MORE REPAINTS OR UPDATES WILL EVER
                // OCCUR
                while(true)
                { 
                    WatchKey key;           
                    try
                    {
                        key = ws.take();
                    }
                    catch (InterruptedException x)
                    {
                        return;
                    }

对不起帽子,但我希望 cmets 脱颖而出。

【讨论】:

  • 知道了,我会试一试。知道为什么 test2 不会附加到列表中吗?我认为它会发生在while循环之前。帽子没有问题:)
  • @GregoryBillings 列表未在此循环中更新,因为您在 ETD 完成此事件处理循环之前阻止了它。重绘只是向 ETD 发出一个请求,要求将来发生作业,但是当您阻止 ETD 时,它永远不会运行;)
【解决方案3】:

1. Swing 不是线程安全的,但像repaint(), setText() 这样的一些方法是Tread Safe

2. Swing 中的 ma​​in() 方法是Not Long Lived。它安排 GUI 的构建在 Event Dispatcher 线程,然后退出。现在它由 EDT 负责处理 GUI。

3.您必须将您的非 UI 工作保持在您的非 UI 线程上,远离 GUI 线程,即 EDT。

4.你的main()方法应该只做使JFrame可见的工作,使用EventQueue.invokeLater.

例如:

    public static void main(String[] args){

       EventQueue.invokeLater(new Runnable(){

       public void run(){

       myFrame.setVisible(true);
     }
  }
}

5 SwingWorker 由 Java 提供,用于在 GUI 线程上同步非 UI 线程的工作输出。

【讨论】:

  • 好点。你能告诉我长寿是什么意思吗?
猜你喜欢
  • 1970-01-01
  • 2011-09-19
  • 2013-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多