c#/c++ 通过系统api监视文件变化的问题

再分享个比较经典的案例,在很多场景下,我们都要去监视某个文件夹下的文件变化,在创建、修改或删除的时候触发一些行为。众所周知,c#有个实现类叫FileSystemWatcher,可以用来监视目录包括子目录下文件的变化,这样就不需要不断的循环去递归扫目录,节省很大的资源开销,而且响应速度也更快。从本质上来说,无论在win还是linux上,都是通过封装系统api进行实现的,所以这个坑,其实是并非是.net封装的问题,而是一个无法绕过的问题。

先来看一个示例demo

using System;
using System.IO;class Program
{static void Main(){// 要监视的目录路径string pathToWatch = @"C:\Path\To\Your\Directory";// 创建一个新的 FileSystemWatcher 实例using (FileSystemWatcher watcher = new FileSystemWatcher()){// 设置要监视的目录watcher.Path = pathToWatch;// 设置要监视的文件和目录的更改类型watcher.NotifyFilter = NotifyFilters.LastWrite| NotifyFilters.FileName| NotifyFilters.DirectoryName;//包含子目录监视watcher.IncludeSubdirectories = true;// 启用事件引发watcher.EnableRaisingEvents = true;// 添加事件处理程序watcher.Created += OnCreated;watcher.Deleted += OnDeleted;watcher.Changed += OnChanged;watcher.Renamed += OnRenamed;// 监视状态Console.WriteLine($"正在监视目录:{pathToWatch}");Console.WriteLine("按任意键退出.");Console.ReadKey();}}// 文件或目录创建事件处理程序private static void OnCreated(object sender, FileSystemEventArgs e){Console.WriteLine($"新文件或目录已创建: {e.Name} - 类型: {e.ChangeType}");}// 文件或目录删除事件处理程序private static void OnDeleted(object sender, FileSystemEventArgs e){Console.WriteLine($"文件或目录已删除: {e.Name} - 类型: {e.ChangeType}");}// 文件或目录更改事件处理程序private static void OnChanged(object sender, FileSystemEventArgs e){Console.WriteLine($"文件或目录已更改: {e.Name} - 类型: {e.ChangeType}");}// 文件或目录重命名事件处理程序private static void OnRenamed(object sender, RenamedEventArgs e){Console.WriteLine($"文件或目录已重命名: {e.OldName} -> {e.Name} - 类型: {e.ChangeType}");}
}

这段示例看起来没有任何问题,但实际使用的时候会发现,有些连续创建的文件,根本扫不到。

测试环境.net6 linux,比如监视AAA文件夹,然后用程序创建AAA\BBB和AAA\BBB\123.txt,会发现能监听到BBB的创建,但却没有AAA\BBB\123.txt的通知。

再来看下windows c++上的demo

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <tchar.h>void RefreshDirectory(LPTSTR);
void RefreshTree(LPTSTR);
void WatchDirectory(LPTSTR);void _tmain(int argc, TCHAR *argv[])
{if(argc != 2){_tprintf(TEXT("Usage: %s <dir>\n"), argv[0]);return;}WatchDirectory(argv[1]);
}void WatchDirectory(LPTSTR lpDir)
{DWORD dwWaitStatus; HANDLE dwChangeHandles[2]; TCHAR lpDrive[4];TCHAR lpFile[_MAX_FNAME];TCHAR lpExt[_MAX_EXT];_tsplitpath_s(lpDir, lpDrive, 4, NULL, 0, lpFile, _MAX_FNAME, lpExt, _MAX_EXT);lpDrive[2] = (TCHAR)'\\';lpDrive[3] = (TCHAR)'\0';// Watch the directory for file creation and deletion. dwChangeHandles[0] = FindFirstChangeNotification( lpDir,                         // directory to watch FALSE,                         // do not watch subtree FILE_NOTIFY_CHANGE_FILE_NAME); // watch file name changes if (dwChangeHandles[0] == INVALID_HANDLE_VALUE) {printf("\n ERROR: FindFirstChangeNotification function failed.\n");ExitProcess(GetLastError()); }// Watch the subtree for directory creation and deletion. dwChangeHandles[1] = FindFirstChangeNotification( lpDrive,                       // directory to watch TRUE,                          // watch the subtree FILE_NOTIFY_CHANGE_DIR_NAME);  // watch dir name changes if (dwChangeHandles[1] == INVALID_HANDLE_VALUE) {printf("\n ERROR: FindFirstChangeNotification function failed.\n");ExitProcess(GetLastError()); }// Make a final validation check on our handles.if ((dwChangeHandles[0] == NULL) || (dwChangeHandles[1] == NULL)){printf("\n ERROR: Unexpected NULL from FindFirstChangeNotification.\n");ExitProcess(GetLastError()); }// Change notification is set. Now wait on both notification 
// handles and refresh accordingly. while (TRUE) { // Wait for notification.printf("\nWaiting for notification...\n");dwWaitStatus = WaitForMultipleObjects(2, dwChangeHandles, FALSE, INFINITE); switch (dwWaitStatus) { case WAIT_OBJECT_0: // A file was created, renamed, or deleted in the directory.// Refresh this directory and restart the notification.RefreshDirectory(lpDir); if ( FindNextChangeNotification(dwChangeHandles[0]) == FALSE ){printf("\n ERROR: FindNextChangeNotification function failed.\n");ExitProcess(GetLastError()); }break; case WAIT_OBJECT_0 + 1: // A directory was created, renamed, or deleted.// Refresh the tree and restart the notification.RefreshTree(lpDrive); if (FindNextChangeNotification(dwChangeHandles[1]) == FALSE ){printf("\n ERROR: FindNextChangeNotification function failed.\n");ExitProcess(GetLastError()); }break; case WAIT_TIMEOUT:// A timeout occurred, this would happen if some value other // than INFINITE is used in the Wait call and no changes occur.// In a single-threaded environment you might not want an// INFINITE wait.printf("\nNo changes in the timeout period.\n");break;default: printf("\n ERROR: Unhandled dwWaitStatus.\n");ExitProcess(GetLastError());break;}}
}void RefreshDirectory(LPTSTR lpDir)
{// This is where you might place code to refresh your// directory listing, but not the subtree because it// would not be necessary._tprintf(TEXT("Directory (%s) changed.\n"), lpDir);
}void RefreshTree(LPTSTR lpDrive)
{// This is where you might place code to refresh your// directory listing, including the subtree._tprintf(TEXT("Directory tree (%s) changed.\n"), lpDrive);
}

可以看到,这段代码里面,有个问题,就是文件夹中如果文件创建在RefreshTree之后,FindNextChangeNotification之前,则会漏掉。所以在dotnet上,实际上并没有使用这种方式,而是通过ReadDirectoryChangesW 去实现的,这种基于buffer的,理论不溢出,就不会出现丢失的情况。所以在windows下,只要buffer足够大,fsw是不会漏掉任何一个文件的。

 那么来到linux,从源码从可以看到是基于inotify的,读下源码第一句话,就真相大白了

所以在linux下,我的那种场景,刚好就触发了这个问题,这种是基于inotify的缺陷,因为这玩意也没buffer,我猜测与上面c++的demo出现的问题类似。

总结一下,使用fsw千万需要小心,在win和linux上的表现是不同的,win上可以放心用,linux上可能会漏文件,需要在自己场景下特定的时间点进行检测。不然可能会触发意想不到的BUG

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/8224.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Lion:闭源大语言模型的对抗性蒸馏

通过调整 70k 指令跟踪数据&#xff0c;Lion (7B) 可以实现 ChatGPT 95% 的能力&#xff01; 消息 我们目前正在致力于训练更大尺寸的版本&#xff08;如果可行的话&#xff0c;13B、33B 和 65B&#xff09;。感谢您的耐心等待。 **[2023年6月10日]**我们发布了微调过程中解…

83、基于STM32单片机录音机录音笔语音存储回放TF卡TFT屏系统设计(程序+原理图+PCB源文件+参考论文+硬件设计资料+元器件清单等)

单片机主芯片选择方案 方案一&#xff1a;AT89C51是美国ATMEL公司生产的低电压&#xff0c;高性能CMOS型8位单片机&#xff0c;器件采用ATMEL公司的高密度、非易失性存储技术生产&#xff0c;兼容标准MCS-51指令系统&#xff0c;片内置通用8位中央处理器(CPU)和Flash存储单元&a…

【Linux】Haproxy搭建Web群集

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 Haproxy搭建Web群集 一、Haproxy概述1.常见的Web集群调度器2.Haproxy应用分析3.Haproxy常用调度算法4.Haproxy的主要特性5.LVS、Nginx、Haproxy的区别 二、Haproxy搭建Web群集…

数据结构与算法_堆排序

堆排序&#xff0c;即利用堆的思想来进行排序。要实现堆排序&#xff0c;首先要建堆&#xff0c;建堆又分为建大堆和建小堆&#xff1b;然后再一步一步地删除堆的元素来进行排序。 目录 一、堆排序的时间复杂度 二、建堆 向上调整 向下调整 三、堆排序 四、代码实现 向…

【案例实战】高并发业务的多级缓存架构一致性解决方案

我们在高并发的项目中基本上都离不开缓存&#xff0c;那么既然引入缓存&#xff0c;那就会有一个缓存与数据库数据一致性的问题。 首先&#xff0c;我们先来看看高并发项目里面Redis常见的三种缓存读写模式。 Cache Aside 读写分离模式&#xff0c;是最常见的Redis缓存模式&a…

react菜鸟教程学习笔记

目录 第一个react实例 react安装 对react机制最直观的理解 如果你第一次用npm 关于初始化package.json的问题 使用 create-react-app 快速构建 React 开发环境 项目目录结构 修改一下代码执行源头APP.js React元素渲染 将元素渲染到DOM中 更新元素渲染 关于vue的更新…

golang 结构体struct转map实践

1、反射 type sign struct { Name string json:"name,omitempty" Age int json:"age,omitempty" } var s sign s.Name "csdn" s.Age 18 //方式1 反射 var data make(map[string]interface{}) t : reflect.TypeOf(s) v : …

记录使用ffmpeg把mp4转换成m3u8

背景:公司需要上一些视频资源,平均每一个都在600m以上,经过考虑以后采取视频分片以后上传到oss上进行加速播放的流程.这里记录一下使用ffmpeg进行转换视频格式的过程中的一些命令. 准备工作: 下载ffmpeg到本地,以及配置ffmpeg到环境变量中,这里就不多说了. 使用的时候先打开…

【IMX6ULL驱动开发学习】14.Linux驱动开发 - GPIO中断(设备树 + GPIO子系统)

代码自取【14.key_tree_pinctrl_gpios_interrupt】&#xff1a; https://gitee.com/chenshao777/imx6-ull_-drivers 主要接口函数&#xff1a; 1. of_gpio_count&#xff08;获得GPIO的数量&#xff09; static inline int of_gpio_count(struct device_node *np)2. kzalloc…

用四元数表示旋转

旋转四元数以及如何使用它们 英文版参考链接:Quaternions 四元数&#xff0c;它是一种用四个实数表示复数的推广&#xff0c;可以用来高效地表示和计算三维空间中的旋转1。 旋转四元数的性质: All rotation quaternions must be unit quaternions.|q| 1For rotation quater…

Elasticsearch:使用 Redis 让 Elasticsearch 更快

Elasticsearch 是一个强大的搜索引擎&#xff0c;可让你快速轻松地搜索大量数据。但是&#xff0c;随着数据量的增长&#xff0c;响应时间可能会变慢&#xff0c;尤其是对于复杂的查询。在本文中&#xff0c;我们将探讨如何使用 Redis 来加快 Elasticsearch 搜索响应时间。 Re…

台阶仪是干什么的?在太阳能光伏行业能测什么?

太阳能作为应用广、无排放、无噪声的环保能源&#xff0c;在近些年迎来快速发展&#xff0c;而在各类型的太阳能电池及太阳能充电系统中&#xff0c;多会镀一层透明的ITO导电薄膜&#xff0c;其镀膜厚度对电池片的导电性能有着非常重要的影响&#xff0c;因而需要对镀膜厚度进行…