LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

c#/c++ 通过系统api-FileSystemWatcher监视文件或目录变化的问题

admin
2023年12月26日 21:59 本文热度 534

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

先来看一个示例demo

1.  using System;

2.  using System.IO;

3.   

4.  class Program

5.  {

6.      static void Main()

7.      {

8.          // 要监视的目录路径

9.          string pathToWatch = @"C:\Path\To\Your\Directory";

10.

11.        // 创建一个新的 FileSystemWatcher 实例

12.        using (FileSystemWatcher watcher = new FileSystemWatcher())

13.        {

14.            // 设置要监视的目录

15.            watcher.Path = pathToWatch;

16.

17.            // 设置要监视的文件和目录的更改类型

18.            watcher.NotifyFilter = NotifyFilters.LastWrite

19.                                 | NotifyFilters.FileName

20.                                 | NotifyFilters.DirectoryName;

21.            //包含子目录监视

22.            watcher.IncludeSubdirectories = true;

23.            // 启用事件引发

24.            watcher.EnableRaisingEvents = true;

25.

26.            // 添加事件处理程序

27.            watcher.Created += OnCreated;

28.            watcher.Deleted += OnDeleted;

29.            watcher.Changed += OnChanged;

30.            watcher.Renamed += OnRenamed;

31.

32.            // 监视状态

33.            Console.WriteLine($"正在监视目录:{pathToWatch}");

34.            Console.WriteLine("按任意键退出.");

35.            Console.ReadKey();

36.        }

37.    }

38.

39.    // 文件或目录创建事件处理程序

40.    private static void OnCreated(object sender, FileSystemEventArgs e)

41.    {

42.        Console.WriteLine($"新文件或目录已创建: {e.Name} - 类型: {e.ChangeType}");

43.    }

44.

45.    // 文件或目录删除事件处理程序

46.    private static void OnDeleted(object sender, FileSystemEventArgs e)

47.    {

48.        Console.WriteLine($"文件或目录已删除: {e.Name} - 类型: {e.ChangeType}");

49.    }

50.

51.    // 文件或目录更改事件处理程序

52.    private static void OnChanged(object sender, FileSystemEventArgs e)

53.    {

54.        Console.WriteLine($"文件或目录已更改: {e.Name} - 类型: {e.ChangeType}");

55.    }

56.

57.    // 文件或目录重命名事件处理程序

58.    private static void OnRenamed(object sender, RenamedEventArgs e)

59.    {

60.        Console.WriteLine($"文件或目录已重命名: {e.OldName} -> {e.Name} - 类型: {e.ChangeType}");

61.    }

62.}

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

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

再来看下windows c++上的demo

1.  #include <windows.h>

2.  #include <stdlib.h>

3.  #include <stdio.h>

4.  #include <tchar.h>

5.   

6.  void RefreshDirectory(LPTSTR);

7.  void RefreshTree(LPTSTR);

8.  void WatchDirectory(LPTSTR);

9.   

10.void _tmain(int argc, TCHAR *argv[])

11.{

12.    if(argc != 2)

13.    {

14.        _tprintf(TEXT("Usage: %s <dir>\n"), argv[0]);

15.        return;

16.    }

17.

18.    WatchDirectory(argv[1]);

19.}

20.

21.void WatchDirectory(LPTSTR lpDir)

22.{

23.   DWORD dwWaitStatus;

24.   HANDLE dwChangeHandles[2];

25.   TCHAR lpDrive[4];

26.   TCHAR lpFile[_MAX_FNAME];

27.   TCHAR lpExt[_MAX_EXT];

28.

29.   _tsplitpath_s(lpDir, lpDrive, 4, NULL, 0, lpFile, _MAX_FNAME, lpExt, _MAX_EXT);

30.

31.   lpDrive[2] = (TCHAR)'\\';

32.   lpDrive[3] = (TCHAR)'\0';

33.

34.// Watch the directory for file creation and deletion.

35.

36.   dwChangeHandles[0] = FindFirstChangeNotification(

37.      lpDir,                         // directory to watch

38.      FALSE,                         // do not watch subtree

39.      FILE_NOTIFY_CHANGE_FILE_NAME); // watch file name changes

40.

41.   if (dwChangeHandles[0] == INVALID_HANDLE_VALUE)

42.   {

43.     printf("\n ERROR: FindFirstChangeNotification function failed.\n");

44.     ExitProcess(GetLastError());

45.   }

46.

47.// Watch the subtree for directory creation and deletion.

48.

49.   dwChangeHandles[1] = FindFirstChangeNotification(

50.      lpDrive,                       // directory to watch

51.      TRUE,                          // watch the subtree

52.      FILE_NOTIFY_CHANGE_DIR_NAME);  // watch dir name changes

53.

54.   if (dwChangeHandles[1] == INVALID_HANDLE_VALUE)

55.   {

56.     printf("\n ERROR: FindFirstChangeNotification function failed.\n");

57.     ExitProcess(GetLastError());

58.   }

59.

60.

61.// Make a final validation check on our handles.

62.

63.   if ((dwChangeHandles[0] == NULL) || (dwChangeHandles[1] == NULL))

64.   {

65.     printf("\n ERROR: Unexpected NULL from FindFirstChangeNotification.\n");

66.     ExitProcess(GetLastError());

67.   }

68.

69.// Change notification is set. Now wait on both notification

70.// handles and refresh accordingly.

71.

72.   while (TRUE)

73.   {

74.   // Wait for notification.

75.

76.      printf("\nWaiting for notification...\n");

77.

78.      dwWaitStatus = WaitForMultipleObjects(2, dwChangeHandles,

79.         FALSE, INFINITE);

80.

81.      switch (dwWaitStatus)

82.      {

83.         case WAIT_OBJECT_0:

84.

85.         // A file was created, renamed, or deleted in the directory.

86.         // Refresh this directory and restart the notification.

87.

88.             RefreshDirectory(lpDir);

89.             if ( FindNextChangeNotification(dwChangeHandles[0]) == FALSE )

90.             {

91.               printf("\n ERROR: FindNextChangeNotification function failed.\n");

92.               ExitProcess(GetLastError());

93.             }

94.             break;

95.

96.         case WAIT_OBJECT_0 + 1:

97.

98.         // A directory was created, renamed, or deleted.

99.         // Refresh the tree and restart the notification.

100.        

101.                    RefreshTree(lpDrive);

102.                    if (FindNextChangeNotification(dwChangeHandles[1]) == FALSE )

103.                    {

104.                      printf("\n ERROR: FindNextChangeNotification function failed.\n");

105.                      ExitProcess(GetLastError());

106.                    }

107.                    break;

108.        

109.                case WAIT_TIMEOUT:

110.        

111.                // A timeout occurred, this would happen if some value other

112.                // than INFINITE is used in the Wait call and no changes occur.

113.                // In a single-threaded environment you might not want an

114.                // INFINITE wait.

115.        

116.                   printf("\nNo changes in the timeout period.\n");

117.                   break;

118.        

119.                default:

120.                   printf("\n ERROR: Unhandled dwWaitStatus.\n");

121.                   ExitProcess(GetLastError());

122.                   break;

123.             }

124.          }

125.       }

126.        

127.       void RefreshDirectory(LPTSTR lpDir)

128.       {

129.          // This is where you might place code to refresh your

130.          // directory listing, but not the subtree because it

131.          // would not be necessary.

132.        

133.          _tprintf(TEXT("Directory (%s) changed.\n"), lpDir);

134.       }

135.        

136.       void RefreshTree(LPTSTR lpDrive)

137.       {

138.          // This is where you might place code to refresh your

139.          // directory listing, including the subtree.

140.        

141.          _tprintf(TEXT("Directory tree (%s) changed.\n"), lpDrive);

142.       }

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

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

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

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


该文章在 2023/12/26 22:12:56 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2024 ClickSun All Rights Reserved