WatchService и SwingWorker: как это правильно сделать? - PullRequest
11 голосов
/ 16 октября 2011

WatchService звучало как увлекательная идея ... к сожалению, она кажется настолько низкой, как было предупреждено в руководстве / api plus, она не совсем вписывается в модель событий Swing (или я упускаю что-то очевидное, не нулевая вероятность

Взяв код из примера WatchDir в учебном пособии (просто для обработки только одного каталога), я в итоге получился

  • продление SwingWorker
  • сделать регистрацию вещей в конструкторе
  • положить бесконечный цикл ожидания ключа в doInBackground
  • публиковать каждое WatchEvent при получении через key.pollEvents ()
  • обработать чанки, запустив propertyChangeEvents с удаленными / созданными файлами как newValue

    @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], за которым наблюдает ключ, но он может передать только одно. Скорее всего, неправильное распределение логики, хотя

[A] Отредактировано (вызвано комментарием @ trashgods) - это на самом деле не ключ, который я должен передавать вместе с событием, это каталог, в котором сообщается об изменениях. Изменил вопрос соответственно

К вашему сведению, этот вопрос находится в перекрестном сообщении на OTN Swing форуме

Добавление

Чтение API-интерфейса WatchKey:

Там, где есть несколько потоков, извлекающих сигнальные ключи из часов затем следует позаботиться о том, чтобы метод сброса вызывается только после обработки событий для объекта.

, кажется, подразумевает, что события должны

  1. обрабатываться в том же потоке, который получил WatchKey
  2. не следует трогать после сброса ключа

Не совсем уверен, но в сочетании с (будущим) требованием рекурсивного просмотра каталогов (более одного) решил последовать совету @Eels, вроде - скоро опубликую код, на котором я остановился

EDIT только что принял мой собственный ответ - смиренно вернет, что если у кого-то есть разумные возражения

Ответы [ 3 ]

4 голосов
/ 21 октября 2011

На самом деле, комментарий @ Eels не переставал стучать мне в затылок - и наконец-то зарегистрировал: это путь, но нет никакой необходимости в "искусственной" структуре, потому что у нас уже есть идеальный кандидат - это собственно PropertyChangeEvent: -)

Если взять общее описание процесса из моего вопроса, первые три маркера остаются неизменными

  • то же самое: расширение SwingWorker
  • то же самое: выполнить регистрацию в конструкторе
  • то же самое: поместить бесконечный цикл в ожидании ключа в doInBackground
  • изменено: создать соответствующий PropertyChangeEvent из каждого WatchEvent при получении через ключ.pollEvents и опубликовать измененное 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 голосов
/ 16 октября 2011

Поскольку ваш фоновый поток полностью посвящен просмотру, take() - правильный выбор.Он эффективно скрывает зависящую от платформы реализацию , которая может либо пересылать, либо опрашивать.Один из методов poll() был бы уместен, если, например, вашему фоновому потоку также необходимо проверить другие очереди последовательно с добавлением WatchService.

: поскольку WatchKey имеет состояние, его, вероятно, не следует пересылать на process().context() из WatchEvent - это путь " относительно между каталогом, зарегистрированным в службе наблюдения, и записью, которая создана, удалена илимодифицированный «.Один из методов resolve() должен работать, если каталоги имеют общий корень.

3 голосов
/ 21 октября 2011

Что касается вашего второго пункта, не могли бы вы создать класс, который бы содержал и WatchEvent, и ключ, и чтобы второй типовой параметр SwingWorker был такого типа?Извините, я знаю, что вы уже подумали об этом, поэтому я думаю, что мой вопрос: есть ли у этого недостаток?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...