Ubuntu下FileSystemWatcher闲置15秒后首次重命名事件未捕获
我之前在Ubuntu环境下用Mono开发时也碰到过完全一样的问题,折腾了好久才搞明白原因——这其实是Mono对Linux系统inotify机制的实现细节导致的,不是你的代码问题。
问题原因分析
在Linux上,Mono的FileSystemWatcher底层依赖系统的inotify API来监听文件事件。为了节省系统资源,Mono默认设置了一个15秒的闲置超时:如果Watcher在15秒内没有捕获到任何事件,它会自动关闭inotify句柄。当再次有文件操作发生时,Watcher会重新初始化inotify,这时候重命名操作的两个动作(删除原文件、创建新文件)会被当成两个独立的事件,无法被关联成Renamed事件。而首次启动时,因为Watcher刚完成初始化,还没建立足够的上下文来关联这两个动作,所以也会出现同样的Created+Deleted替代Renamed的情况。
可行的解决方法
方法1:禁用Mono的inotify闲置超时
这是最直接的解决方案,通过设置环境变量让Mono永远不关闭inotify句柄:
- 在终端运行程序前,先执行:
export MONO_INOTIFY_POLLING_TIMEOUT=-1 - 如果是在MonoDevelop中运行程序,可以打开项目的运行配置,在环境变量列表里添加
MONO_INOTIFY_POLLING_TIMEOUT,值设为-1。
设置后,Watcher会一直保持活跃状态,不管闲置多久,重命名事件都会被正确捕获,首次启动也能正常触发Renamed事件。
方法2:用后台线程保持Watcher活跃
如果无法修改环境变量(比如部署环境限制),可以在代码中添加一个后台线程,定期在监控目录下执行一个无意义的文件操作(创建并删除临时文件),防止Watcher因为闲置而关闭inotify句柄:
在你的Run方法中,开启Watcher之后添加以下代码:
using System.Threading; // ... 现有代码 ... // Begin watching. watcher.EnableRaisingEvents = true; // 添加保持活跃的后台线程 Thread keepAliveThread = new Thread(() => { while (true) { Thread.Sleep(10000); // 每10秒执行一次,小于15秒的超时时间 string tempFile = Path.Combine(watcher.Path, ".fs-watcher-keepalive.tmp"); try { // 创建临时文件并立即释放资源 using (var fs = File.Create(tempFile)) {} // 删除临时文件 File.Delete(tempFile); } catch (Exception ex) { // 忽略权限不足、目录被删除等异常,不影响主逻辑 Console.WriteLine($"Keep-alive operation failed: {ex.Message}"); } } }); keepAliveThread.IsBackground = true; // 后台线程随主程序退出 keepAliveThread.Start(); // Wait for the user to quit the program. Console.WriteLine("Press 'q' to quit the sample."); while(Console.Read()!='q');
这个线程会每隔10秒触发一次微小的文件事件,让Watcher一直处于活跃状态,不会触发闲置超时。
额外建议
如果你使用的是Ubuntu 16.04自带的旧版本Mono,建议升级到最新的稳定版Mono(比如通过Mono官方源安装),后续版本对FileSystemWatcher的inotify实现做了不少优化,可能会减少这类问题的出现。
内容的提问来源于stack exchange,提问作者Sydrik

