WatchService неправильно опрашивает

Я хотел бы опрашивать каталог каждые 10 секунд, чтобы узнать, были ли добавлены или изменены какие-либо файлы. Если в течение 10 секунд произошли какие-либо изменения, я хотел бы иметь набор всех путей к файлам, которые я могу затем передать другому методу.

Проблема

Когда файл добавляется, он мгновенно распознается и вызывается метод addedFiles. Вместо этого я ожидал бы, что он подождет 10 секунд и вызовет метод addedFiles с несколькими найденными файлами.

Пример
Я создал полный пример, который отслеживает каталог. Затем поток ждет 5 секунд и копирует 2000 файлов в отслеживаемый каталог.
Ожидаемое поведение WatchService заключается в проверке изменений каждые 10 секунд. Вместо этого он, кажется, мгновенно улавливает изменения.

Код

import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.Collection;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;

public class DirectoryWatcherExample 
{
    private static final int POLLING_TIME = 10;

    public static void main(final String args[]) throws InterruptedException, IOException
    {
        final Path directory = Paths.get("directory/to/be/watched");

        /**
         * Start a thread that will create 2000 files to the selected directory
         * This will occur after waiting 5 seconds.
         */
        new Thread(new Runnable()
        {
            @Override
            public void run() 
            {
                try 
                {
                    Thread.sleep(5000);         
                    System.out.println("Copying 2000 files to directory: " + directory);
                    for(int i = 0; i < 2000; i++)
                    {
                        final PrintWriter writer = new PrintWriter(directory.resolve("test_file_" + i + ".txt").toFile(), "UTF-8");
                        writer.println("The first line");
                        writer.println("The second line");
                        writer.close();
                    }
                    System.out.println("Finished copying files to directory: " + directory);
                } 
                catch (final Exception e) 
                {
                    e.printStackTrace();
                } 
            }
        }).start();

        /**
         * Start the watch service polling every 10 seconds
         */
        new DirectoryWatcherExample().startWatchService(directory);
    }

    public void startWatchService(final Path directory) throws InterruptedException, IOException
    {
        final WatchService watchService = FileSystems.getDefault().newWatchService();
        directory.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY);

        while(true)
        {
            System.out.println("Start polling");
            final WatchKey key = watchService.poll(POLLING_TIME, TimeUnit.SECONDS);
            System.out.println("Finished polling and retrieved key");

            if(key != null)
            {
                final Collection<Path> paths = new HashSet<>();
                for (final WatchEvent<?> watchEvent : key.pollEvents())
                {
                    final Path path = ((Path) key.watchable()).resolve((Path) watchEvent.context());
                    paths.add(path);
                    System.out.println("Path added: " + path);
                }

                // Do something with the paths
                addedFiles(paths);

                if (!key.reset())
                {
                    break;
                }   
            }

        }
    }

    // Unimplemented
    public void addedFiles(final Collection<Path> paths)
    {

    }
}

Что может быть причиной этого?


person Michael    schedule 22.11.2018    source источник
comment
Можете ли вы объяснить мне, почему вы использовали Thread.sleep(5000)? Связано ли это с размером файла, предположим, у нас есть файл, который достаточно хорош для спящего режима 5000 мс, но что, если появится файл огромного размера? Нужно ли нам больше времени сна или это только для того, чтобы остановить поток на несколько раз перед запуском процесса? Объясните пожалуйста у меня похожая проблема   -  person sh4r4d    schedule 18.11.2020
comment
@ sh4r4d Я создал этот вопрос 2 года назад, так что могу ошибаться. Похоже, я заставляю его спать, чтобы он запустил службу часов до того, как начал добавлять файлы. Вероятно, я мог бы избежать сна и просто поместить метод запуска наблюдения над кодом потока.   -  person Michael    schedule 18.11.2020
comment
Значит, Sleep не для размера файла, а для запуска службы просмотра перед добавлением файлов? Я сталкиваюсь с той же проблемой: когда я создаю новый файл, он должен вызвать CREATE, но даже до того, как CREATE завершится, запускается MODIFY.   -  person sh4r4d    schedule 18.11.2020


Ответы (2)


Есть два варианта:

  1. Вам нужно вызвать poll на watchService после определенного интервала, засыпая между ними. Как указывали другие, тайм-аут в методе poll предназначен для сценариев, когда в буфере нет доступных событий. Кроме того, поскольку вы не обрабатываете события немедленно, некоторые события могут переполнить буфер операционной системы и в конечном итоге потеряться. Следовательно, вам также необходимо обрабатывать сценарий переполнения.

  2. В качестве альтернативы вы можете использовать библиотеку мониторинга файлов ввода-вывода Apache Commons. Он опрашивает файловую систему, как вы хотите. Вы даже можете установить интервал опроса.

Обратитесь к следующим трем классам/интерфейсам здесь:

  • FileAlterationMonitor — это в основном поток (реализация Runnable), который спит в течение интервала опроса и после каждого интервала вызывает FileAlterationObserver
  • FileAlterationObserver — перечисляет файлы в каталоге, сравнивает текущий список с предыдущим списком, идентифицирует изменения файла и вызывает соответствующий метод в реализации FileAlterationListener
  • FileAlterationListener — Интерфейс, который вам нужен для реализации и написания вашей логики

Что вы можете сделать для своего варианта использования, так это продолжать добавлять все сведения о файле в список по мере их добавления или изменения. Наконец, когда вызывается метод onStop(), вы вызываете свой метод addedFiles с полным списком, очищаете список и начинаете заново.

person Saptarshi Basu    schedule 22.11.2018
comment
Это имеет смысл. Оба отличных ответа, но вы также объясняете альтернативы. Спасибо вам за помощь! - person Michael; 23.11.2018

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

Он по-прежнему возвращается, как только обнаруживает изменение. Прочитайте JavaDoc для WatchService.poll

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

Нигде не написано, что всегда будет ждать так долго.

person Thomas Kläger    schedule 22.11.2018
comment
Я понимаю. Каким будет правильный подход для опроса каждые n секунды? Добавляете Thread.sleep в цикл while? Главное, чего я хотел избежать, — это прослушивания сообщений от ОС, указывающих, произошли ли изменения в файловой системе. Я думал, что это будет просто хранить список текущих файлов и сравнивать их каждый раз, когда он будет опрашиваться. - person Michael; 22.11.2018
comment
@Michael the WatchService был специально создан, чтобы код Java мог реагировать на уведомления файловой системы от ОС. Если вы хотите иметь список текущих файлов и сравнивать их через определенные промежутки времени, вам нужно будет реализовать это самостоятельно. - person Thomas Kläger; 22.11.2018