11

WatchService 听起来像是一个令人兴奋的想法......不幸的是,它似乎与教程/api 中警告的一样低级,而且并不真正适合 Swing 事件模型(或者我遗漏了一些明显的东西,不是零概率

从教程中的 WatchDir 示例中获取代码(简化为仅处理单个目录),我基本上结束了

  • 扩展 SwingWorker
  • 在构造函数中做注册的东西
  • 在 doInBackground 中放置等待键的无限循环
  • 通过 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);
                }
            }
        }
    
    }
    

基本思想是让使用代码幸福地不知道粘糊糊的细节:它监听属性更改并根据需要更新任意模型:

    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);

好像有效果,但是感觉不舒服

  • 将我自己作为线程不可知论者:到目前为止,我看到的所有示例片段都使用 watcher.take() 阻塞了等待线程。他们为什么这样做?预计至少有些人会使用 watcher.poll() 并睡一会儿。
  • SwingWorker 发布方法似乎不太适合:现在没关系,因为我只在看一个目录(不想在错误的方向上飞得太远 :) 尝试观看多个目录时(如原始 WatchDir 示例)有几个键和相对于其中之一的 WatchEvent。要解析路径,我需要事件和密钥正在监视的目录[A] - 但只能传递一个。不过,很可能逻辑的分布是错误的

[A]已编辑(由@trashgods 的评论触发) - 实际上这不是我必须与事件一起传递的密钥,而是它报告更改的目录。相应地改变了问题

仅供参考,这个问题是交叉发布到OTN swing 论坛

附录

阅读 WatchKey 的 api 文档:

如果有多个线程从监视服务检索信号键,则应注意确保仅在处理对象的事件后调用重置方法。

似乎暗示这些事件应该

  1. 在检索 WatchKey 的同一线程上进行处理
  2. 重置钥匙后不应触摸

不完全确定,但结合递归观看目录(多个)的(未来)要求,决定遵循@Eels 的建议,有点 - 很快就会发布我确定的代码

编辑 刚刚接受了我自己的答案 - 如果有人有合理的反对意见,我会谦虚地回复

4

3 回答 3

4

因为你的后台线程完全用于观看,take()是正确的选择。它有效地隐藏了依赖于平台的实现,可以转发或轮询。例如,如果您的poll()后台线程还需要检查与WatchService.

附录:因为WatchKeyhas 状态,它可能不应该被转发到process(). context()aWatchEvent是“向监视服务注册的目录与创建、删除或修改的条目之间的相对路径”。resolve()如果目录共享一个公共根目录,则其中一种方法应该有效。

于 2011-10-16T16:58:32.420 回答
4

实际上,@Eels 的评论并没有停止敲打我的后脑勺——最后注册了:这是要走的路,但不需要任何“人造”结构,因为我们已经有了完美的候选人——它是 PropertyChangeEvent本身:-)

从我的问题中获取整个过程描述,前三个项目符号保持不变

  • 相同:扩展 SwingWorker
  • 相同:在构造函数中进行注册
  • 相同:在 doInBackground 中放置等待键的无限循环
  • 已更改:通过 key.pollEvents 检索时从每个 WatchEvent 创建适当的 PropertyChangeEvent 并发布 PropertyChangeEvent
  • 更改:在进程(块)中触发先前创建的事件

修订FileWorker

@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);
        }
    }
}
于 2011-10-21T12:04:31.713 回答
3

关于你的第二点,你不能创建一个同时包含 WatchEvent 和 key 的类,并使 SwingWorker 的第二个泛型参数是这种类型吗?抱歉,我知道您已经想到了这一点,所以我想我的问题是:这样做有什么缺点吗?

于 2011-10-21T11:55:35.267 回答