我在使用netbeans Swing GUI时出现Threads问题。这是我第一次真正尝试使用Java的文件系统通知程序为备份程序开发GUI。我有两个文件SyncUI.java
和Sync.java
。
我想要发生的事情就是在jTextField1
文本字段中输入目录路径,该路径创建一个同步线程,创建一个新的同步对象,然后在该对象上调用processEvents
。当更改该目录中的文件时,我想将有关更改的文本添加到列表中。
在当前状态下,UI不再响应,但processEvents
未在我的列表中添加任何内容。知道问题是什么吗?此外,由于我刚开始使用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
}
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;
}
}
}
});
}
}
答案 0 :(得分:6)
Swing不是线程安全的,因此,如果您尝试更新同一线程中的UI,则会出现“应用程序冻结”行为。要解决此问题,您需要将更新UI的过程委派给另一个线程。这是使用SwingUtilities.invokeLater(Java 5和先前)方法和/或SwingWorker类(自Java 6开始)创建的。
一些链接:
Google搜索: https://www.google.com.br/search?q=swing+thread+safe
答案 1 :(得分:4)
支持davidbuzatto:
不,我认为你错过了InvokeLater
所做的事。 InvokeLater
确保在ETD
上执行runnable。所以基本上,从我可以阅读的内容来看,你已经把你长期运行的事件阻止代码放回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;
}
对不起,但我希望评论能够突出。
答案 2 :(得分:2)
1。 Swing 不线程安全,但repaint(), setText()
等某些方法 Tread Safe 。
2。 Swing中的 main()方法不长期。它安排了GUI的构建 事件Dispactcher线程然后退出。现在它是EDT处理GUI的责任。
3。您必须将非UI工作保留在非UI线程上,不在GUI线程,即EDT。
4。您的main()
方法应该只使用EventQueue.invokeLater.
<强>例如强>
public static void main(String[] args){
EventQueue.invokeLater(new Runnable(){
public void run(){
myFrame.setVisible(true);
}
}
}
5 Java提供了SwingWorker来同步 GUI线程上非UI线程的工作输出。