Java WatchService和SwingWorker:如何正确执行?

Java WatchService和SwingWorker:如何正确执行?,java,swing,nio,swingworker,watchservice,Java,Swing,Nio,Swingworker,Watchservice,WatchService听起来像是一个激动人心的想法。。。不幸的是,它似乎和教程/api plus中警告的一样低级,并不真正适合Swing事件模型(或者我遗漏了一些明显的东西,不是零概率) 以代码(简化为只处理一个目录)为例,我基本上以 扩展SwingWorker 在构造函数中进行注册 将无休止的等待钥匙的循环放在后台 通过key.pollEvents()检索时发布每个WatchEvent 通过将已删除/创建的文件作为newValue触发propertyChangeEvents来处理区块 @S

WatchService听起来像是一个激动人心的想法。。。不幸的是,它似乎和教程/api plus中警告的一样低级,并不真正适合Swing事件模型(或者我遗漏了一些明显的东西,不是零概率)

以代码(简化为只处理一个目录)为例,我基本上以

  • 扩展SwingWorker
  • 在构造函数中进行注册
  • 将无休止的等待钥匙的循环放在后台
  • 通过key.pollEvents()检索时发布每个WatchEvent
  • 通过将已删除/创建的文件作为newValue触发propertyChangeEvents来处理区块

    @SuppressWarnings("unchecked")
    public class FileWorker extends SwingWorker<Void, WatchEvent<Path>> {
    
        public static final String DELETED = "deletedFile";
        public static final String CREATED = "createdFile";
    
        private Path directory;
        private WatchService watcher;
    
        public FileWorker(File file) throws IOException {
            directory = file.toPath();
            watcher = FileSystems.getDefault().newWatchService();
            directory.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        }
    
        @Override
        protected Void doInBackground() throws Exception {
            for (;;) {
                // wait for key to be signalled
                WatchKey key;
                try {
                    key = watcher.take();
                } catch (InterruptedException x) {
                    return null;
                }
    
                for (WatchEvent<?> event : key.pollEvents()) {
                    WatchEvent.Kind<?> kind = event.kind();
                    // TBD - provide example of how OVERFLOW event is handled
                    if (kind == OVERFLOW) {
                        continue;
                    }
                    publish((WatchEvent<Path>) event);
                }
    
                // reset key return if directory no longer accessible
                boolean valid = key.reset();
                if (!valid) {
                    break;
                }
            }
            return null;
        }
    
        @Override
        protected void process(List<WatchEvent<Path>> chunks) {
            super.process(chunks);
            for (WatchEvent<Path> event : chunks) {
                WatchEvent.Kind<?> kind = event.kind();
                Path name = event.context();
                Path child = directory.resolve(name);
                File file = child.toFile();
                if (StandardWatchEventKinds.ENTRY_DELETE == kind) {
                    firePropertyChange(DELETED, null, file);
                } else if (StandardWatchEventKinds.ENTRY_CREATE == kind) {
                    firePropertyChange(CREATED, null, file);
                }
            }
        }
    
    }
    
    @SuppressWarnings(“未选中”)
    公共类FileWorker扩展SwingWorker{
    公共静态最终字符串DELETED=“deletedFile”;
    创建的公共静态最终字符串=“createdFile”;
    专用路径目录;
    私人观察者;
    公共文件工作者(文件)引发IOException{
    directory=file.toPath();
    watcher=FileSystems.getDefault().newWatchService();
    目录。注册(观察者、条目创建、条目删除、条目修改);
    }
    @凌驾
    受保护的Void doInBackground()引发异常{
    对于(;;){
    //等待钥匙发出信号
    监视键;
    试一试{
    key=watcher.take();
    }捕捉(中断异常x){
    返回null;
    }
    for(WatchEvent事件:key.pollEvents()){
    WatchEvent.Kind-Kind=event.Kind();
    //TBD-提供如何处理溢出事件的示例
    如果(种类==溢出){
    继续;
    }
    发布((监视事件)事件);
    }
    //如果目录不再可访问,则重置键返回
    布尔有效值=key.reset();
    如果(!有效){
    打破
    }
    }
    返回null;
    }
    @凌驾
    受保护的无效进程(列表块){
    超级进程(块);
    for(WatchEvent事件:块){
    WatchEvent.Kind-Kind=event.Kind();
    路径名=event.context();
    Path child=directory.resolve(名称);
    File=child.toFile();
    if(StandardWatchEventTypes.ENTRY_DELETE==种类){
    firePropertyChange(已删除,空,文件);
    }else if(StandardWatchEventTypes.ENTRY_CREATE==种类){
    firePropertyChange(已创建,空,文件);
    }
    }
    }
    }
    
其基本思想是让使用代码的人完全不知道这些不清晰的细节:它会监听属性更改,并根据需要更新任意模型:

    String testDir = "D:\\scans\\library";
    File directory = new File(testDir);
    final DefaultListModel<File> model = new DefaultListModel<File>();
    for (File file : directory.listFiles()) {
        model.addElement(file);
    }
    final FileWorker worker = new FileWorker(directory);
    PropertyChangeListener l = new PropertyChangeListener() {

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if (FileWorker.DELETED == evt.getPropertyName()) {
                model.removeElement(evt.getNewValue());
            } else if (FileWorker.CREATED == evt.getPropertyName()) {
                model.addElement((File) evt.getNewValue());
            }
        }
    };
    worker.addPropertyChangeListener(l);
    JXList list = new JXList(model);
String testDir=“D:\\scans\\library”;
文件目录=新文件(testDir);
最终DefaultListModel=新DefaultListModel();
对于(文件:directory.listFiles()){
模型补遗(文件);
}
最终文件工作者=新文件工作者(目录);
PropertyChangeListener l=新的PropertyChangeListener(){
@凌驾
公共作废属性更改(属性更改事件evt){
if(FileWorker.DELETED==evt.getPropertyName()){
model.removelement(evt.getNewValue());
}else if(FileWorker.CREATED==evt.getPropertyName()){
model.addElement((文件)evt.getNewValue());
}
}
};
worker.addPropertyChangeListener(l);
JXList列表=新JXList(型号);
看起来不错,但我觉得不舒服

  • 把自己打扮成线程不可知论者:到目前为止,我看到的所有示例代码片段都会使用watcher.take()阻止等待的线程。为什么要这样做?至少会有一些人使用watcher.poll()并睡一会儿
  • SwingWorker发布方法似乎不太合适:目前还可以,因为我在尝试查看多个目录(如最初的WatchDir示例中)时只查看一个目录(不想走错太远的方向:)有多个键以及与其中一个键相关的WatchEvent。要解析路径,我需要事件和键正在监视的目录[A]。但只能传递一个。不过,很可能是逻辑分布错误
[A] 编辑(由@trashgoods的评论触发)-这实际上不是我在活动中必须传递的关键,而是它报告更改的目录。相应地更改了问题

仅供参考,此问题已交叉发布到

附录

读取WatchKey的api文档:

其中有多个线程从手表检索信号键 然后,应注意确保重置方法正确 仅在处理对象的事件后调用

似乎暗示事件应该

  • 在检索WatchKey的同一线程上处理
  • 钥匙复位后不应触摸
  • 不完全确定,但结合(未来)递归监视目录(不止一个)的要求,决定遵循@Eels建议,有点-将很快发布我确定的代码

    编辑
    刚刚接受了我自己的回答-将谦恭地回复,如果有人有合理的反对意见,因为您的后台线程完全用于观看,这是正确的选择。它有效地隐藏了实现,可以转发或轮询。例如,如果您的后台线程也eded检查与
    监视服务
    串联的其他队列

    附录:由于具有状态,可能不应将其转发到
    process()
    。a的路径是“在watch服务中注册的目录和
    @SuppressWarnings("unchecked")
    public class FileWorker extends SwingWorker<Void, PropertyChangeEvent> {
    
        public static final String FILE_DELETED = StandardWatchEventKinds.ENTRY_DELETE.name();
        public static final String FILE_CREATED = StandardWatchEventKinds.ENTRY_CREATE.name();
        public static final String FILE_MODIFIED = StandardWatchEventKinds.ENTRY_MODIFY.name();
    
        // final version will keep a map of keys/directories (just as in the tutorial example) 
        private Path directory;
        private WatchService watcher;
    
        public FileWorker(File file) throws IOException {
            directory = file.toPath();
            watcher = FileSystems.getDefault().newWatchService();
            directory.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        }
    
        @Override
        protected Void doInBackground() throws Exception {
            for (;;) {
                // wait for key to be signalled
                WatchKey key;
                try {
                    key = watcher.take();
                } catch (InterruptedException x) {
                    return null;
                }
    
                for (WatchEvent<?> event : key.pollEvents()) {
                    WatchEvent.Kind<?> kind = event.kind();
                    // TBD - provide example of how OVERFLOW event is handled
                    if (kind == OVERFLOW) {
                        continue;
                    }
                    publish(createChangeEvent((WatchEvent<Path>) event, key));
                }
    
                // reset key return if directory no longer accessible
                boolean valid = key.reset();
                if (!valid) {
                    break;
                }
            }
            return null;
        }
    
        /**
         * Creates and returns the change notification. This method is called from the 
         * worker thread while looping through the events as received from the Watchkey.
         * 
         * @param event
         * @param key
         */
        protected PropertyChangeEvent createChangeEvent(WatchEvent<Path> event, WatchKey key) {
            Path name = event.context();
            // real world will lookup the directory from the key/directory map
            Path child = directory.resolve(name);
            PropertyChangeEvent e = new PropertyChangeEvent(this, event.kind().name(), null, child.toFile());
            return e;
        }
    
        @Override
        protected void process(List<PropertyChangeEvent> chunks) {
            super.process(chunks);
            for (PropertyChangeEvent event : chunks) {
                getPropertyChangeSupport().firePropertyChange(event);
            }
        }
    }