问题 WatchService和SwingWorker:如何正确地做到这一点?


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

基本的想法是使用代码幸福地不知道粘糊糊的细节:它听取属性的变化和f.i.根据需要更新任意模型:

    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]关键是观看 - 但只能传递一个。但是,最有可能错误地分配逻辑

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

仅供参考,这个问题是交叉发布的 OTN摇摆论坛

附录

阅读WatchKey的api文档:

有几个线程从手表中检索信号键   服务然后应该注意确保重置方法   仅在处理了对象的事件后调用。

似乎暗示事件应该

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

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

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


10251
2017-10-16 14:28


起源

关于你的第二点,难道你不能创建一个同时包含WatchEvent和key并且让SwingWorker的第二个泛型参数成为这种类型的类吗?对不起,我知道你已经想过这个了,所以我想我的问题是:这样做有什么不妥之处吗? - Hovercraft Full Of Eels
@Hovercraft Full Of Eels实际上只是在这里感觉不好,它只是一个结构,当我认为我需要类似这样的结构时,我的警钟开始响起......所以我可能做错了什么:-) - kleopatra
+1问题,让我们的Java Essential Classes管理java.util.Xxx,并且(如果不是另一种方式)输入/输出到这些进程使用SwingWorker或简单的javax.swing.Actions做得更好,没有阴影桥到Swing_to_Future - mKorbel
@Hovercraft Full Of Eels考虑给你的建议一个答案,所以我可以投票给它 - 它是我的解决方案的触发器:-) - kleopatra
@kleo:完成,谢谢。发布最终代码后,请务必标记垃圾和我! - Hovercraft Full Of Eels


答案:


实际上,@ 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);
        }
    }
}

4
2017-10-21 12:04



你不能用char' - '在代码块之前,我把dot放在这里 separator - mKorbel
啊...隐藏的规则,我怎么样 爱 他们;-)感谢@trashgod,@ mKorbel! - kleopatra
我的要求是观看文件夹,一旦文件被添加/写入/移动到文件夹中,立即对其执行操作(例如,通过电子邮件发送文件)。我遇到的问题是,当文件很大时,可能需要一段时间才能完成写入或复制,而一旦文件的第一个字节写入文件夹就会宣布FILE_CREATE事件。所以我不能立即执行该操作。在对文件执行任何操作之前,确定文件是否已完全写入的可靠方法是什么? - Web User
我们拥有的唯一证据是客户日志。我自己还没看过。但似乎,是的......当你遇到溢出时,正确的行为是关闭所有内容并重新开始爬行文件树。对我来说,所有这些令人遗憾的部分是闪亮的新API充满漏洞,当你插入所有漏洞时,你可能只是抓取了目录。另外,根据平台的不同,实现可能会天真地爬行目录! - Trejkaz
顺便说一句,我已经准备好接受这个溢出的东西,“一个特定于操作系统的问题,有时操作系统将没有足够的空间来存储事件”,直到阅读源代码,才发现引入限制的代码是在JDK本身。仅编码为512个事件 - 并且无法以任何方式进行自定义。 - Trejkaz


因为你的背景线程完全专注于观看, take() 是正确的选择。它有效地隐藏了 平台依赖 实施,可以转发或轮询。其中一个 poll() 例如,如果你的后台线程还需要检查与其串联的其他队列,那么这些方法是合适的 WatchService

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


3
2017-10-16 16:58



免责声明:我没试过这个,但模式很熟悉。 - trashgod
感谢分享您的见解:-) - kleopatra
别客气。我上面已经详细说明了,但是有多个根的文件系统是 terra incognita 对我来说。如果您在OTN上进行更多实验或获取某些内容,我会欢迎更具决定性的答案。 - trashgod
关键的好点。事实证明,我不需要它,但它报告更改的目录。编辑了这个问题,谢谢 - kleopatra
谢谢,帮助我提出了(最终?至少目前足够好:)答案 - kleopatra


关于你的第二点,难道你不能创建一个同时包含WatchEvent和key并且让SwingWorker的第二个泛型参数成为这种类型的类吗?对不起,我知道你已经想过这个了,所以我想我的问题是:这样做有什么不妥之处吗?


3
2017-10-21 11:55



谢谢,帮助我提出了(最终?至少目前足够好:)答案 - kleopatra
[OT]刚刚看到一个古老的(他们都严格来说)飞行马戏团的一集 - 提到 您 <G> - kleopatra
@kleo:谢谢。听到这个让我的乳头高兴得爆炸! - Hovercraft Full Of Eels