【问题标题】:C# and the FileSystemWatcherC# 和 FileSystemWatcher
【发布时间】:2020-08-13 13:22:41
【问题描述】:

我用 C# 编写了一个服务,它应该将备份文件(*.bak 和 *.trn)从数据库服务器移动到特殊的备份服务器。到目前为止,这工作得很好。问题是它尝试移动单个文件两次。这当然失败了。我已将 FileSystemWatcher 配置如下:

try
{
    m_objWatcher = new FileSystemWatcher();
    m_objWatcher.Filter = m_strFilter;
    m_objWatcher.Path = m_strSourcepath.Substring(0, m_strSourcepath.Length - 1);
    m_objWatcher.IncludeSubdirectories = m_bolIncludeSubdirectories;
    m_objWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.LastAccess; // | NotifyFilters.CreationTime;
    m_objWatcher.Changed += new FileSystemEventHandler(objWatcher_OnCreated);
}
catch (Exception ex)
{
    m_objLogger.d(TAG, m_strWatchername + "InitFileWatcher(): " + ex.ToString());
}

Watcher 是否有可能为同一个文件产生两次事件?如果我只将过滤器设置为 CreationTime,它根本不会做出反应。

如何将 Watcher 设置为每个文件只触发一次事件?

提前感谢您的帮助

【问题讨论】:

  • 也许是因为你的 NotifyFilter?您同时使用 LastWrite 和 LastAccess 过滤器,因此当文件更改时,它们都会更改并且 FileSystemWatcher 会抛出 2 次事件?您可以尝试删除其中一个并分享结果吗?
  • The documentation 明确指出常见的文件系统操作可能引发多个事件。检查事件和缓冲区大小部分。

标签: c# events service filesystemwatcher


【解决方案1】:

The documentation 指出常见的文件系统操作可能引发多个事件。检查事件和缓冲区大小标题下。

常见的文件系统操作可能引发多个事件。例如,当一个文件从一个目录移动到另一个目录时,可能会引发几个 OnChanged 以及一些 OnCreated 和 OnDeleted 事件。移动文件是一项复杂的操作,由多个简单的操作组成,因此会引发多个事件。同样,某些应用程序(例如,防病毒软件)可能会导致 FileSystemWatcher 检测到的其他文件系统事件。

它还提供了一些指南,包括:

让您的事件处理代码尽可能短。

为此,您可以使用FileSystemWatcher.Changed 事件将文件排队等待处理,然后再处理它们。这是一个使用System.Threading.Timer 实例处理队列的简单示例。

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;

public class ServiceClass
{
    public ServiceClass()
    {
        _processing = false;
        _fileQueue = new ConcurrentQueue<string>();
        _timer = new System.Threading.Timer(ProcessQueue);
        // Schedule the time to run in 5 seconds, then again every 5 seconds.
        _timer.Change(5000, 5000);
    }

    private void objWatcher_OnChanged(object sender, FileSystemEventArgs e)
    {
        // Just queue the file to be processed later. If the same file is added multiple
        // times, we'll skip the duplicates when processing the files.
        _fileQueue.Enqueue(e.FilePath);
    }

    private void ProcessQueue(object state)
    {
        if (_processing)
        {
            return;
        }
        _processing = true;
        var failures = new HashSet<string>();
        try
        {
            while (_fileQueue.TryDequeue(out string fileToProcess))
            {
                if (!File.Exists(fileToProcess))
                {
                    // Probably a file that was added multiple times and it was
                    // already processed.
                    continue; 
                }
                var file = new FileInfo(fileToProcess);
                if (FileIsLocked(file))
                {
                    // File is locked. Maybe you got the Changed event, but the file
                    // wasn't done being written.
                    failures.Add(fileToProcess);
                    continue;
                }
                try
                {
                    fileInfo.MoveTo(/*Your destination*/);
                }
                catch (Exception)
                {
                    // File failed to move. Add it to the failures so it can be tried
                    // again.
                    failutes.Add(fileToProcess);
                }
            }
        }
        finally
        {
            // Add any failures back to the queue to try again.
            foreach (var failedFile in failures)
            {
                _fileQueue.Enqueue(failedFile);
            }
            _processing = false;
        }
    }

    private bool IsFileLocked(FileInfo file)
    {
        try
        {
            using (FileStream stream = file.Open(FileMode.Open, FileAccess.Read,
               FileShare.None))
            {
                stream.Close();
            }
        }
        catch (IOException)
        {
            return true;
        }
        return false;
    }

    private System.Threading.Timer _timer;
    private bool _processing;
    private ConcurrentQueue<string> _fileQueue;
}

归功于它应得的,我从this answer那里拿走了FileIsLocked

您可能需要考虑的其他一些事项:

如果您的FileSystemWatcher 错过了活动会怎样? [文档]确实声明这是可能的。

请注意,当超出缓冲区大小时,FileSystemWatcher 可能会错过事件。为避免错过活动,请遵循以下准则:

通过设置 InternalBufferSize 属性增加缓冲区大小。

避免使用长文件名查看文件,因为长文件名会导致缓冲区被填满。考虑使用较短的名称重命名这些文件。

让您的事件处理代码尽可能短。

如果您的服务崩溃,但写入备份文件的进程继续写入它们会怎样?当您重新启动服务时,它会拾取这些文件并移动它们吗?

【讨论】:

    【解决方案2】:

    我尝试了各种想法来阻止这种情况。事件太接近了......它无法在 FileChanged 事件中停止。这是我的工作解决方案:

        private System.Timers.Timer timer;
        private FileSystemWatcher fwatcher;
    
        static void Main(string[] args)
        {
            new Program();
        }
    
        private Program()
        {
            timer = new System.Timers.Timer(100);
            timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
            timer.AutoReset = false; // only once
    
            fwatcher = new FileSystemWatcher();
            fwatcher.Path = filePath;
            fwatcher.Filter = fileName;
            fwatcher.NotifyFilter = NotifyFilters.LastWrite;
            fwatcher.Changed += new FileSystemEventHandler(FileChanged);
            fwatcher.EnableRaisingEvents = true;
    
            while (IsRunning)
            {
                Thread.Sleep(100);
            }
            Thread.Sleep(100);
        }
        
        private void FileChanged(object sender, FileSystemEventArgs e)
        {
            timer.Start();
        }
    
        private void OnTimedEvent(object source, ElapsedEventArgs e)
        {
            Console.WriteLine("file has changed!");
        }
    

    每次更改文件时计时器只会触发一次。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-16
      • 2021-08-13
      相关资源
      最近更新 更多