You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

如何在C#中向FileSystemWatcher监控路径写入文件并避免无限循环

解决C# FileSystemWatcher替换文件时的无限循环问题

这个场景我太熟悉了——FileSystemWatcher监控文件夹,当外部修改文件时用备份覆盖,但自己的写入又触发了Watcher事件,陷入循环死胡同。核心就是要区分程序自身的写入和外部的未授权修改,下面给你几个实用的解决方案:

方案一:临时禁用事件监听(最简单直接)

在执行文件替换操作前,先关闭FileSystemWatcher的事件触发,完成后再重新开启。注意要加锁保证线程安全,因为Watcher的事件是在后台线程触发的,防止多个事件同时执行导致状态混乱。

修改你的代码如下:

// 定义一个全局锁对象,保证线程安全
private readonly object _watcherLock = new object();

public FileSystemWatcher CreateAndExecute(string path) 
{ 
    Console.WriteLine("Watching " + path); 
    FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(); 
    fileSystemWatcher.Path = path; 
    fileSystemWatcher.IncludeSubdirectories = false; 
    fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; 
    fileSystemWatcher.Filter = "*.txt"; 
    fileSystemWatcher.Changed += OnChange; 
    fileSystemWatcher.InternalBufferSize = 32768; 
    fileSystemWatcher.EnableRaisingEvents = true;
    return fileSystemWatcher; // 注意这里要返回watcher对象,否则后续无法操作
} 

private void OnChange(object source, FileSystemEventArgs e) 
{ 
    lock (_watcherLock)
    {
        var watcher = (FileSystemWatcher)source;
        // 先禁用事件触发,避免自身写入触发循环
        watcher.EnableRaisingEvents = false;
        
        try
        {
            // 从数据库读取备份副本并写入文件,替换成你的实际数据库读取逻辑
            string backupContent = GetBackupContentFromDatabase(e.FullPath); 
            File.WriteAllText(e.FullPath, backupContent);
            Console.WriteLine($"Restored file: {e.FullPath}");
        }
        finally
        {
            // 无论操作成功与否,都要恢复事件监听
            watcher.EnableRaisingEvents = true;
        }
    }
}

优缺点:实现简单,但禁用监听期间如果有外部修改,Watcher会错过这些事件。如果你的场景对实时性要求不是极高,这个方案足够用。

方案二:用标记变量区分自身操作(平衡简单与可靠)

定义一个布尔标记,在执行恢复操作时设为true,事件触发时先检查这个标记,如果是自身操作就直接跳过。同样要加锁保证线程安全。

代码示例:

private bool _isRestoringFile = false;
private readonly object _restoreLock = new object();

private void OnChange(object source, FileSystemEventArgs e) 
{ 
    lock (_restoreLock)
    {
        // 如果当前正在执行恢复操作,直接跳过事件处理
        if (_isRestoringFile)
        {
            return;
        }
        
        _isRestoringFile = true;
        try
        {
            // 执行备份替换逻辑,替换成你的实际数据库读取逻辑
            string backupContent = GetBackupContentFromDatabase(e.FullPath);
            File.WriteAllText(e.FullPath, backupContent);
            Console.WriteLine($"Restored file: {e.FullPath}");
        }
        finally
        {
            // 重置标记,确保后续事件正常处理
            _isRestoringFile = false;
        }
    }
}

优缺点:不用禁用监听,不会错过外部事件,实现也不复杂,是大多数场景下的首选方案。

方案三:基于文件哈希判断(最可靠)

通过计算文件内容的哈希值,判断当前触发事件的文件是否是刚恢复的备份副本。如果哈希和备份一致,说明是自身写入,直接跳过。

代码示例:

// 记录每个文件的备份哈希,用于后续比对
private readonly Dictionary<string, string> _fileBackupHashes = new Dictionary<string, string>();
private readonly object _hashLock = new object();

private void OnChange(object source, FileSystemEventArgs e) 
{ 
    lock (_hashLock)
    {
        string currentFileHash = CalculateFileHash(e.FullPath);
        string backupContent = GetBackupContentFromDatabase(e.FullPath);
        string backupHash = CalculateStringHash(backupContent);
        
        // 如果当前文件哈希和备份哈希一致,说明是自身刚写入的,跳过
        if (currentFileHash == backupHash)
        {
            return;
        }
        
        // 执行替换操作
        File.WriteAllText(e.FullPath, backupContent);
        // 更新哈希记录
        if (_fileBackupHashes.ContainsKey(e.FullPath))
        {
            _fileBackupHashes[e.FullPath] = backupHash;
        }
        else
        {
            _fileBackupHashes.Add(e.FullPath, backupHash);
        }
        Console.WriteLine($"Restored file: {e.FullPath}");
    }
}

// 计算文件的MD5哈希
private string CalculateFileHash(string filePath)
{
    using (var md5 = MD5.Create())
    {
        using (var stream = File.OpenRead(filePath))
        {
            byte[] hashBytes = md5.ComputeHash(stream);
            return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
        }
    }
}

// 计算字符串的MD5哈希
private string CalculateStringHash(string content)
{
    using (var md5 = MD5.Create())
    {
        byte[] contentBytes = Encoding.UTF8.GetBytes(content);
        byte[] hashBytes = md5.ComputeHash(contentBytes);
        return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
    }
}

优缺点:完全基于内容判断,不会因为线程问题出现误判,适合对准确性要求极高的场景,但需要额外的哈希计算,性能上略有损耗。

总结

如果你的场景对实时性要求一般,方案一足够简单;追求平衡的话方案二是首选;如果需要绝对可靠的判断,就用方案三。

内容的提问来源于stack exchange,提问作者Eddy

火山引擎 最新活动