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

基本的な考え方は、コードを使用して、ぬるぬるした詳細を幸福に認識しないようにすることです。プロパティの変更をリッスンし、fiは必要に応じて任意のモデルを更新します。

    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のpublishメソッドは完全には適合していないようです。今のところ、1つのディレクトリだけを監視しているので、問題ありません(間違った方向にあまり遠くまでギャロップしたくありませんでした:)複数のディレクトリを監視しようとする場合(元のWatchDirの例)いくつかのキーとそれらの1つに関連するWatchEventがあります。パスを解決するには、イベントとキーが監視しているディレクトリ[A]の両方が必要ですが、渡すことができるのは1つだけです。おそらく、ロジックの分散が間違っている可能性がありますが

[A]編集済み(@trashgodsのコメントによってトリガーされます)-実際には、イベントと一緒に渡さなければならないキーではなく、変更を報告しているディレクトリです。それに応じて質問を変更しました

参考までに、この質問はOTNスイングフォーラムにクロスポストされています

補遺

WatchKeyのAPIドキュメントを読む:

ウォッチサービスからシグナルキーを取得するスレッドが複数ある場合は、オブジェクトのイベントが処理された後にのみリセットメソッドが呼び出されるように注意する必要があります。

イベントがすべきであることを意味するようです

  1. WatchKeyを取得したのと同じスレッドで処理されます
  2. キーがリセットされた後は触れないでください

完全にはわかりませんが、@ Eelsのアドバイスに従うことを決定したディレクトリ(複数)を再帰的に監視するという(将来の)要件と組み合わせると、私が決めたコードがすぐに投稿されます

EDIT は私自身の答えを受け入れました-誰かが合理的な異議を唱えた場合、それを謙虚に元に戻します

4

3 に答える 3

4

バックグラウンド スレッドは完全に監視に専念しているためtake()、正しい選択です。転送またはポーリングする可能性のあるプラットフォーム依存の実装を効果的に隠します。たとえば、poll()バックグラウンド スレッドがWatchService.

補遺:WatchKeyは状態があるため、おそらく に転送しないでくださいprocess()。のcontext()WatchEvent、「監視サービスに登録されたディレクトリと、作成、削除、または変更されたエントリとの間の相対パス」です。resolve()ディレクトリが共通のルートを共有している場合、いずれかの方法が機能するはずです。

于 2011-10-16T16:58:32.420 に答える
4

実際、@Eels のコメントは私の頭の後ろをノックするのを止めませんでした-そして最終的に登録されました:それは行くべき道ですが、「人工的な」構造体は必要ありません。すでに完璧な候補があるためです-それはPropertyChangeEventそれ自体:-)

私の質問から全体的なプロセスの説明を取得すると、最初の 3 つの箇条書きは同じままです

  • 同じ: 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

2 点目ですが、WatchEvent とキーの両方を保持するクラスを作成し、SwingWorker の 2 番目のジェネリック パラメータをこの型にすることはできないでしょうか。申し訳ありませんが、あなたはすでにこれについて考えていることを知っているので、私の質問は次のとおりです。これを行うことのマイナス面はありますか?

于 2011-10-21T11:55:35.267 に答える