功能说明
开发服务器、配置热加载和同步工具需要感知文件变化。操作系统通知往往一次修改触发多个事件,例如创建、写入、重命名各一条,编辑器保存还可能先生成临时文件。直接逐条处理会重复构建或读到半写状态。本例把事件收集到通道,主循环记录最后一次事件时间,只有安静超过五百毫秒才认为一批变化结束。
notify 的 watcher 必须在整个监听期间保持存活,一旦被丢弃,底层监听就会停止。WatchHandle 把 watcher 与接收端放在一起,并用下划线前缀字段表明它只为生命周期而存在。演示递归监听当前目录,两秒内没有任何事件时正常退出。
关键代码
watch 创建标准库通道,把发送端移入事件回调,回调忽略错误结果,只转发成功事件。RecommendedWatcher::new 使用默认配置,watch 指定递归模式。主循环用 recv_timeout 每两百毫秒醒来一次,既可处理事件,也能检查静默时间。收到事件就刷新 last 并累加计数;有事件且静默超过五百毫秒时输出批次数量并退出。没有事件且启动超过两秒时输出提示退出,避免演示永久阻塞。
使用注意
编辑器通常采用原子保存,先写临时文件再重命名,因此监听目录可能收到临时文件事件,应过滤扩展名和临时前缀。递归监听大目录会消耗较多文件描述符,应限制深度或使用忽略规则。不同平台的事件类型与合并行为不同,不能假设一定收到写入事件。通道无界,事件暴增时可能积压内存,生产实现应使用有界通道并丢弃重复事件。防抖窗口结束后仍应校验文件是否可读和内容是否稳定。
验证方式
运行脚本后,在另一终端向当前目录写入或修改一个文件,程序应打印若干 raw event,随后在约五百毫秒静默后输出 debounced batch 并退出。不做任何修改时,两秒后输出 no changes observed。连续快速修改多个文件,应只输出一批,且数量反映收到的事件条数。
代码示例
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
use std::path::Path;
use std::sync::mpsc::{channel, Receiver};
use std::time::{Duration, Instant};
struct WatchHandle {
_watcher: RecommendedWatcher,
events: Receiver<Event>,
}
fn watch(path: &Path) -> notify::Result<WatchHandle> {
let (sender, events) = channel();
let mut watcher = RecommendedWatcher::new(
move |result: notify::Result<Event>| {
if let Ok(event) = result {
let _ = sender.send(event);
}
},
Config::default(),
)?;
watcher.watch(path, RecursiveMode::Recursive)?;
Ok(WatchHandle {
_watcher: watcher,
events,
})
}
fn main() -> notify::Result<()> {
let handle = watch(Path::new("."))?;
let started = Instant::now();
let mut last = Instant::now();
let mut pending = false;
let mut count = 0;
loop {
match handle.events.recv_timeout(Duration::from_millis(200)) {
Ok(event) => {
last = Instant::now();
pending = true;
count += 1;
println!("raw event: {:?}", event.kind);
}
Err(_) if pending && last.elapsed() >= Duration::from_millis(500) => {
println!("debounced batch of {count} events ready");
break;
}
Err(_) if !pending && started.elapsed() >= Duration::from_secs(2) => {
println!("no changes observed");
break;
}
Err(_) => {}
}
}
Ok(())
}