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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值