Java AWT线程

5

我在使用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 - Andrew Thompson
是的,我能够在我的按钮按下操作监听器中执行list.add("stuff");,并且它确实将我的条目附加到列表中。此外,如果我在processEvents中的while(true)循环之前返回,它也会附加到列表中。 - Greg
1
哦,对了,忘记了 while(true) 的部分。好的,不要阻塞EDT(事件派发线程)- 当发生这种情况时,GUI将会“冻结”。 对于长时间运行的任务,请使用SwingWorker。有关更多详细信息,请参见 Swing中的并发性 - Andrew Thompson
是的,我知道我需要进行这些更改。GUI并不像它将会是什么样子,只是一个我随便拼凑起来尝试让核心功能运行的基本示例。 - Greg
@GregoryBillings 请不要将轻量级和重量级组件混合在一起使用(java.awt.List 是重量级组件)。当然,我知道是可以做到的,但这总是会变得非常混乱 :P - MadProgrammer
显示剩余3条评论
3个回答

8

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线程不再被阻塞。问题是现在列表不会更新。 - Greg
GregoryBillings,请看看@MadProgrammer的回答 ;) - davidbuzatto

4
支持 davidbuzatto:
不,我认为你误解了 InvokeLater 的作用。 InvokeLater 确保可运行程序在 ETD 上执行。 所以基本上,从我所读到的内容来看,你已经将长时间运行的、事件阻塞的代码放回了 ETD 中。只有当你想要更新 UI 时才使用 InvokeLater,当你想要实际处理时,请使用线程或 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;
                    }

抱歉使用大写字母,但我想让评论更加突出。


好的,我会试一下。不过你知道为什么test2没有被添加到列表中吗?我认为它应该会被添加进去,因为它在while循环之前。大小写没问题 :) - Greg
1
@GregoryBillings列表在此循环中未被更新,因为您在完成此事件处理周期之前阻止了ETD。Repaint只是将一个请求放入ETD以便在未来执行作业,但由于您阻止了ETD,它永远不会运行 ;) - MadProgrammer

2

1. Swing 不是线程安全的,但有些方法,如 repaint(),setText() 是线程安全的。

2. Swing 中的 main() 方法并不长寿。它会在事件分发线程中安排 GUI 的构建,然后退出。现在责任在 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);
     }
  }
}

Java提供了SwingWorker来同步非UI线程的工作输出到GUI线程。


不错的观点。你能告诉我“not long lived”是什么意思吗? - JavaTechnical

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接