简体   繁体   English

FileSystemWatcher:忽略对文件的第一次更改

[英]FileSystemWatcher: Ignore first change to the file

I am using FileSystemWatcher class in C# to track the changes on a file whenever the user saves the file after modifying it.我在C#中使用FileSystemWatcher类来跟踪用户在修改文件后保存文件时的更改。

FileSystemWatcher watcher = new FileSystemWatcher()
{
    Path = DIR_NAME,
    NotifyFilter = NotifyFilters.LastWrite | 
                   NotifyFilters.CreationTime | 
                   NotifyFilters.LastAccess,
    Filter = "*.pdf",
    IncludeSubdirectories = true,
    EnableRaisingEvents = true
};

watcher.Changed += OnChanged;

However, the file that I want to track is getting created programmatically, as follows:但是,我要跟踪的文件是以编程方式创建的,如下所示:

FileStream stream = FileUtil.CreateNewFile(filePath); // Creates a file
file.WriteFileToStream(stream); // Writes into the file

Ideally, my code is supposed to run as follows:理想情况下,我的代码应该按如下方式运行:

  1. The program will create a file and write some content into it.该程序将创建一个文件并向其中写入一些内容。
  2. User opens the file, modifies it and saves.用户打开文件,修改并保存。
  3. At this point, it should trigger OnChanged , ie I want my OnChanged handler code to execute only when a real user modifies it and saves it.此时,它应该触发OnChanged ,即我希望我的OnChanged处理程序代码仅在真实用户修改并保存它时执行。

However, it's getting triggered whenever the file is getting written into programmatically, ie on the following line:但是,只要以编程方式写入文件,它就会被触发,即在以下行中:

file.WriteFileToStream(stream);

Which is, technically, correct behavior, as it's tracking the change in the file.从技术上讲,这是正确的行为,因为它正在跟踪文件中的更改。 However, my business case doesn't allow the OnChanged handler code to be executed when the file is initially created and written into.但是,我的业务案例不允许在最初创建和写入文件时执行OnChanged处理程序代码。

My question is, is there a workaround to skip the OnChanged call when the file is created and written into first time programmatically?我的问题是,是否有一种解决方法可以在第一次以编程方式创建和写入文件时跳过OnChanged调用?

Note:笔记:

  1. The application architecture requires that the FileSystemWatcher is initialized when my application starts.应用程序架构要求在我的应用程序启动时初始化FileSystemWatcher So I cannot register it after the file is created.所以我无法在创建文件后注册它。
  2. This is a multi-user application, where multiple users will be writing into the files simultaneously, so I cannot disable the watcher before creating the file and enable it after its created:这是一个多用户应用程序,其中多个用户将同时写入文件,因此我无法在创建文件之前禁用观察器并在创建文件后启用它:

watcher.EnableRaisingEvents = false; 
CreateFile();
watcher.EnableRaisingEvents = true;

Approach One:方法一:

  1. Create and save the file in a directory that is not being watched.创建文件并将其保存在未被监视的目录中。
  2. Move the file into the directory being watched.将文件移动到正在监视的目录中。

Approach Two:方法二:

When a file is created, use OnCreated() event handler to add the file name to a filesToIgnore list.创建文件时,使用OnCreated()事件处理程序将文件名添加到filesToIgnore列表。

In the OnChanged event handler, check if the filesToIgnore list contains the file name.OnChanged事件处理程序中,检查filesToIgnore列表是否包含文件名。 If it does, remove it from the list (to process it next time) and return from the handler.如果是,则将其从列表中删除(以便下次处理)并从处理程序返回。

private List<string> filesToIgnore = new List<string>();

private void OnCreated(object source, FileSystemEventArgs file)
{
   filesToIgnore.Add(file.Name);
}

private void OnChanged(object source, FileSystemEventArgs file)
{
    if(filesToIgnore.Contains(file.Name))
    {
         filesToIgnore.Remove(file.Name);
         return; 
    }

    // Code to execute when user saves the file
}

This approach assumes that OnCreated() will be always triggered before OnChanged().此方法假定OnCreated()将始终在OnChanged().

This is a sticky situation and there is not much you can do about it apart from checking if the file is locked (assuming it has a read or write lock):这是一个棘手的情况,除了检查文件是否被锁定(假设它有读锁或写锁)之外,您无能为力:

public  bool IsFileLocked(string fileName)
{

   try
   {
      using (var stream = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.None))
      {
         return false;
      }   
   }
   catch (IOException)
   {
      //the file is unavailable
      return true;
   }
}

The premise is when you get an update, you check if the file is locked, if it isn't then you can loosely assume it's been closed.前提是当你获得更新时,你检查文件是否被锁定,如果不是那么你可以粗略地假设它已被关闭。

Some things to consider though:但需要考虑一些事项:

  1. There could be a race condition when checking the file is locked, getting the all clear, and then finding out some other process has locked the file.当检查文件是否被锁定、清除所有内容,然后发现其他进程已锁定该文件时,可能存在竞争条件。
  2. I have used FileAccess.Read , so this doesn't fail on read only files.我使用过FileAccess.Read ,所以这不会在只读文件上失败。
  3. Events can be dropped from the FileSystemWatcher for various reasons, and it can't always be relied on in certain situation;由于各种原因,事件可能会从FileSystemWatcher中删除,并且在某些情况下不能总是依赖它; read the documentation for the reasons why events can be dropped and how to fix them.阅读文档以了解可以删除事件的原因以及如何修复它们。 In these cases, you could possible get away with a long poll and some logic to pick up any stragglers (depending on your situation).在这些情况下,您可以通过长时间的轮询和一些逻辑来挑选掉队的人(取决于您的情况)。

I added the following code in OnChanged handler, and it seems to be working as expected.我在OnChanged处理程序中添加了以下代码,它似乎按预期工作。

private void OnChanged(object source, FileSystemEventArgs file)
{
    if (file.ChangeType == WatcherChangeTypes.Created)
        return;

    FileInfo fileInfo = new FileInfo(file.FullPath);
    if (fileInfo.CreationTime == fileInfo.LastWriteTime)
        return; 

    // Handle the Changed event
    ...
}

However, I am not sure if I am missing something that will cause this to break.但是,我不确定我是否遗漏了一些会导致它崩溃的东西。 Any thoughts?有什么想法吗?

The extension method bellow allows handling events from user's actions only:下面的扩展方法只允许处理来自用户操作的事件:

public static void OnChangedByUser(this FileSystemWatcher fsw,
    FileSystemEventHandler handler)
{
    const int TOLERANCE_MSEC = 100;
    object locker = new object();
    string fileName = null;
    Stopwatch stopwatch = new Stopwatch();
    fsw.Created += OnFileCreated;
    fsw.Changed += OnFileChanged;
    fsw.Disposed += OnDisposed;
    void OnFileCreated(object sender, FileSystemEventArgs e)
    {
        lock (locker)
        {
            fileName = e.Name;
            stopwatch.Restart();
        }
    }
    void OnFileChanged(object sender, FileSystemEventArgs e)
    {
        lock (locker)
        {
            if (e.Name == fileName && stopwatch.ElapsedMilliseconds < TOLERANCE_MSEC)
            {
                return; // Ignore this event
            }
        }
        handler.Invoke(sender, e);
    }
    void OnDisposed(object sender, EventArgs e)
    {
        fsw.Created -= OnFileCreated;
        fsw.Changed -= OnFileChanged;
        fsw.Disposed -= OnDisposed;
    }
}

Usage example:使用示例:

var fsw = new FileSystemWatcher(DIR_NAME);
fsw.OnChangedByUser(File_ChangedByUser);
fsw.EnableRaisingEvents = true;

private static void File_ChangedByUser(object sender, FileSystemEventArgs e)
{
    // Handle the event
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM