当前位置: 首页 > 知识库问答 >
问题:

Java 7 WatchService-忽略同一事件的多次发生

颜楚青
2023-03-14

StandardWatchEventKinds的javadoc。条目_MODIFY显示:

目录条目已修改。当为此事件注册目录时,当观察到目录中的条目已被修改时,WatchKey将排队。此事件的事件计数为1或更大。

通过编辑器编辑文件内容时,它会同时修改日期(或其他元数据)和内容。因此,您将获得两个ENTRY\u MODIFY事件,但每个事件的count值为1(至少我看到的是这样)。

我试图监视配置文件(servers.cfg以前注册的WatchService),手动更新(即通过命令行vi)以下代码:

while(true) {
    watchKey = watchService.take(); // blocks

    for (WatchEvent<?> event : watchKey.pollEvents()) {
        WatchEvent<Path> watchEvent = (WatchEvent<Path>) event;
        WatchEvent.Kind<Path> kind = watchEvent.kind();

        System.out.println(watchEvent.context() + ", count: "+ watchEvent.count() + ", event: "+ watchEvent.kind());
        // prints (loop on the while twice)
        // servers.cfg, count: 1, event: ENTRY_MODIFY
        // servers.cfg, count: 1, event: ENTRY_MODIFY

        switch(kind.name()) {
            case "ENTRY_MODIFY":
                handleModify(watchEvent.context()); // reload configuration class
                break;
            case "ENTRY_DELETE":
                handleDelete(watchEvent.context()); // do something else
                break;              
        }
    }   

    watchKey.reset();       
}

由于您会得到两个ENTRY\u MODIFY事件,因此当只需要一次时,上述事件将重新加载配置两次。假设可能有不止一个这样的事件,有没有办法忽略除其中一个之外的所有事件?

如果WatchServiceAPI有这样的实用程序就更好了。(我有点不想检查每个事件之间的时间。我代码中的所有处理程序方法都是同步的。

如果您从一个目录创建(复制/粘贴)一个文件到监视目录,也会发生同样的事情。您如何将这两者组合成一个事件?

共有3个答案

云骏奇
2023-03-14

对于此类问题,我的goto解决方案之一是简单地将唯一事件资源排队并延迟处理可接受的时间。在这种情况下,我维护一个Set

每次有有趣的事件发生时,我都会将文件名添加到集合中

addFileToProcess()和processFiles()方法是“同步”的,以确保不会引发ConcurrentModificationException。

这个简化/独立的示例是Oracle WatchDir的衍生产品。爪哇:

import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

public class DirectoryWatcherService implements Runnable {
    @SuppressWarnings("unchecked")
    static <T> WatchEvent<T> cast(WatchEvent<?> event) {
        return (WatchEvent<T>)event;
    }

    /*
     * Wait this long after an event before processing the files.
     */
    private final int DELAY = 500;

    /*
     * Use a SET to prevent duplicates from being added when multiple events on the 
     * same file arrive in quick succession.
     */
    HashSet<String> filesToReload = new HashSet<String>();

    /*
     * Keep a map that will be used to resolve WatchKeys to the parent directory
     * so that we can resolve the full path to an event file. 
     */
    private final Map<WatchKey,Path> keys;

    Timer processDelayTimer = null;

    private volatile Thread server;

    private boolean trace = false;

    private WatchService watcher = null;

    public DirectoryWatcherService(Path dir, boolean recursive) 
        throws IOException {
        this.watcher = FileSystems.getDefault().newWatchService();
        this.keys = new HashMap<WatchKey,Path>();

        if (recursive) {
            registerAll(dir);
        } else {
            register(dir);
        }

        // enable trace after initial registration
        this.trace = true;
    }

    private synchronized void addFileToProcess(String filename) {
        boolean alreadyAdded = filesToReload.add(filename) == false;
        System.out.println("Queuing file for processing: " 
            + filename + (alreadyAdded?"(already queued)":""));
        if (processDelayTimer != null) {
            processDelayTimer.cancel();
        }
        processDelayTimer = new Timer();
        processDelayTimer.schedule(new TimerTask() {

            @Override
            public void run() {
                processFiles();
            }
        }, DELAY);
    }

    private synchronized void processFiles() {
        /*
         * Iterate over the set of file to be processed
         */
        for (Iterator<String> it = filesToReload.iterator(); it.hasNext();) {
            String filename = it.next();

            /*
             * Sometimes you just have to do what you have to do...
             */
            System.out.println("Processing file: " + filename);

            /*
             * Remove this file from the set.
             */
            it.remove();
        }
    }

    /**
     * Register the given directory with the WatchService
     */
    private void register(Path dir) throws IOException {
        WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        if (trace) {
            Path prev = keys.get(key);
            if (prev == null) {
                System.out.format("register: %s\n", dir);
            } else {
                if (!dir.equals(prev)) {
                    System.out.format("update: %s -> %s\n", prev, dir);
                }
            }
        }
        keys.put(key, dir);
    }

    /**
     * Register the given directory, and all its sub-directories, with the
     * WatchService.
     */
    private void registerAll(final Path start) throws IOException {
        // register directory and sub-directories
        Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                throws IOException
            {
                if (dir.getFileName().toString().startsWith(".")) {
                    return FileVisitResult.SKIP_SUBTREE;
                }

                register(dir);
                return FileVisitResult.CONTINUE;
            }
        });
    }

    @SuppressWarnings("unchecked")
    @Override
    public void run() {
        Thread thisThread = Thread.currentThread();

        while (server == thisThread) {
            try {
                // wait for key to be signaled
                WatchKey key;
                try {
                    key = watcher.take();
                } catch (InterruptedException x) {
                    return;
                }

                Path dir = keys.get(key);
                if (dir == null) {
                    continue;
                }

                for (WatchEvent<?> event: key.pollEvents()) {
                    WatchEvent.Kind<?> kind = event.kind();

                    if (kind == OVERFLOW) {
                        continue;
                    }

                    if (kind == ENTRY_MODIFY) {

                        WatchEvent<Path> ev = (WatchEvent<Path>)event;
                        Path name = ev.context();
                        Path child = dir.resolve(name);

                        String filename = child.toAbsolutePath().toString();

                        addFileToProcess(filename);
                    }
                }

                key.reset();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void start() {
        server = new Thread(this);
        server.setName("Directory Watcher Service");
        server.start();
    }


    public void stop() {
        Thread moribund = server;
        server = null;
        if (moribund != null) {
            moribund.interrupt();
        }
    }

    public static void main(String[] args) {
        if (args==null || args.length == 0) {
            System.err.println("You need to provide a path to watch!");
            System.exit(-1);
        }

        Path p = Paths.get(args[0]);
        if (!Files.isDirectory(p)) {
            System.err.println(p + " is not a directory!");
            System.exit(-1);
        }

        DirectoryWatcherService watcherService;
        try {
            watcherService = new DirectoryWatcherService(p, true);
            watcherService.start();
        } catch (IOException e) {
            System.err.println(e.getMessage());
        }
    }

}

虞修平
2023-03-14

我有一个类似的问题——我使用WatchService API来保持目录同步,但观察到在许多情况下,更新被执行了两次。我似乎通过检查文件上的时间戳解决了这个问题——这似乎屏蔽了第二次复制操作。(至少在windows 7中——我不能确定它是否能在其他操作系统中正常工作)

也许你可以用类似的东西?存储文件中的时间戳,并仅在时间戳更新时重新加载?

呼延子安
2023-03-14

WatcherServices会报告两次事件,因为基础文件会更新两次。一次用于内容,一次用于文件修改时间。这些事件发生在短时间内。要解决这个问题,请在poll()take()调用和键之间Hibernate。pollEvents()调用。例如:

@Override
@SuppressWarnings( "SleepWhileInLoop" )
public void run() {
  setListening( true );

  while( isListening() ) {
    try {
      final WatchKey key = getWatchService().take();
      final Path path = get( key );

      // Prevent receiving two separate ENTRY_MODIFY events: file modified
      // and timestamp updated. Instead, receive one ENTRY_MODIFY event
      // with two counts.
      Thread.sleep( 50 );

      for( final WatchEvent<?> event : key.pollEvents() ) {
        final Path changed = path.resolve( (Path)event.context() );

        if( event.kind() == ENTRY_MODIFY && isListening( changed ) ) {
          System.out.println( "Changed: " + changed );
        }
      }

      if( !key.reset() ) {
        ignore( path );
      }
    } catch( IOException | InterruptedException ex ) {
      // Stop eavesdropping.
      setListening( false );
    }
  }
}

调用sleep()有助于消除双重调用。延迟可能必须高达三秒。

 类似资料:
  • 问题内容: 的javadoc 说: 目录条目已修改。当为此事件注册目录时,如果观察到目录中的条目已被修改,则WatchKey将排队。此事件的事件计数为1或更大。 通过编辑器编辑文件的内容时,它将同时修改日期(或其他元数据)和内容。因此,您将获得两个事件,但每个事件都将具有1(至少是我所看到的)。 我正在尝试监视使用以下代码手动更新(即通过命令行)的配置文件(先前已向进行注册): 由于您收到了两个事

  • 我有一个 Blob 存储容器,其中配置了事件网格触发器(Blob 已创建)。我正在通过数据工厂加载此 blob 存储文件,很多时候,许多文件可能会在一次尝试中出现在此 blob 中。也许我们可以举一个20个文件的例子。 好消息是我的事件网格触发器启动了,函数app被调用。然而,我发现有时对于同一个文件,事件网格触发器被触发了不止一次。 在这20个文件中,很少有文件非常大,比如300 MB,但其他文

  • 我的Python Azure函数配置文件()定义了一个Blob存储触发器。 当Azure功能唤醒时(即,Live Metrics中显示的服务器在一段睡眠时间后变为在线),它将处理所有现有Blob,而不管哪些Blob已经生成触发事件。 我注意到< code > azure-web jobs-hosts/blob receipts 文件夹中填充了< code>sandboxhost637nnn文件夹。

  • 我对TextWatcher有一个恼人的问题。我一直在网上搜索,但什么也找不到。如果有人能帮助我,我将不胜感激。 由于某些原因,在一次文本更改时对TextWatcher事件的调用是不稳定的。有时它们被触发一次(就像它们应该被触发的那样),有时两次,有时三次。不知道为什么,整个事情都很直截了当。有时,PostTextChanged()上的可编辑参数在toString()和length()中返回空值。

  • 我将AWS S3触发器配置为带有PUT操作的lambda函数。 每2分钟上载100KB大小的.txt文件。 有时S3会以相同的事件和时间触发lambda两次。 触发器1: 触发器2: 我怎样才能避免这种情况呢?

  • 问题内容: 我试图用Javascript编写视频扑克游戏,以降低其基础知识,但是我遇到了一个问题,其中jQuery click事件处理程序多次触发。 它们被附加到用于下注的按钮上,并且对于在游戏过程中第一手下注(仅触发一次)非常有效。但是在秒针下注中,每次按下一个下注或下注按钮都会触发两次点击事件(因此,每次按下正确的赌注量是两次)。总体而言,在按一次下注按钮时,触发单击事件的次数遵循此模式序列的