You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FileSystemWatcher重复触发事件:如何实现文件仅复制一次?

问题解答

1. 事件连续触发两次的原因

  • 文件系统写入机制:多数程序创建文件时会分两步执行——先创建临时文件写入内容,完成后重命名为目标文件。这两个操作都会触发Created事件,导致两次通知。
  • FileSystemWatcher的通知特性:操作系统会针对文件创建的不同阶段(比如文件句柄创建、内容写入完成)发送多个通知,Watcher会全部捕获。
  • 自身操作的连锁触发:如果你的FileSystemWatcher开启了IncludeSubdirectories=true,那么事件中创建子目录、复制文件的操作,可能会触发额外的Created通知(针对子目录或复制后的文件)。
  • 时间格式潜在问题:你用DateTime.Now.ToString("dddd, dd MMMM yyyy")生成目录名,若两次触发间隔跨日期(比如深夜23:59到00:00)会生成不同目录名;若第二次触发时源文件已被系统释放/删除,也会引发异常。

2. 实现仅复制一次的解决方案

方案1:添加已处理文件缓存

用线程安全集合记录已处理的文件路径,避免重复操作:

// 线程安全集合存储已处理文件路径
private readonly HashSet<string> _processedFiles = new HashSet<string>();
private readonly object _lockObj = new object();

private void OnCreated(object sender, FileSystemEventArgs e)
{
    this.Invoke(new Action(delegate ()
    {
        lock (_lockObj)
        {
            // 跳过已处理文件或不存在的源文件
            if (_processedFiles.Contains(e.FullPath) || !File.Exists(e.FullPath))
                return;
            _processedFiles.Add(e.FullPath);
        }

        RichTextBoxExtensions.AppendText(richTextBox1, $"Created: {e.FullPath}", Color.GreenYellow);
        RichTextBoxExtensions.AppendText(richTextBox1, $" On: {DateTime.Now}", Color.Yellow);
        richTextBox1.AppendText(Environment.NewLine);

        // 用Path.Combine替代字符串拼接,避免路径错误
        string folderName = Path.Combine(savedGamesPath, $"Save Game {DateTime.Now:dddd, dd MMMM yyyy}");
        Directory.CreateDirectory(folderName); // 无需提前判断目录是否存在
        string destFile = Path.Combine(folderName, e.Name);

        try
        {
            File.Copy(e.FullPath, destFile);
        }
        catch (Exception ex)
        {
            RichTextBoxExtensions.AppendText(richTextBox1, $"复制失败: {ex.Message}", Color.Red);
            richTextBox1.AppendText(Environment.NewLine);
            // 移除缓存,允许后续重试(可选)
            lock (_lockObj)
            {
                _processedFiles.Remove(e.FullPath);
            }
        }
    }));
}

方案2:延迟处理,等待文件稳定

第一次触发可能是临时文件,延迟几秒再处理,确保文件完全创建完成:

private void OnCreated(object sender, FileSystemEventArgs e)
{
    // 延迟1秒处理,避免捕获临时文件触发的事件
    Task.Delay(1000).ContinueWith(_ =>
    {
        this.Invoke(new Action(delegate ()
        {
            if (!File.Exists(e.FullPath))
                return;

            RichTextBoxExtensions.AppendText(richTextBox1, $"Created: {e.FullPath}", Color.GreenYellow);
            RichTextBoxExtensions.AppendText(richTextBox1, $" On: {DateTime.Now}", Color.Yellow);
            richTextBox1.AppendText(Environment.NewLine);

            string folderName = Path.Combine(savedGamesPath, $"Save Game {DateTime.Now:dddd, dd MMMM yyyy}");
            Directory.CreateDirectory(folderName);
            string destFile = Path.Combine(folderName, e.Name);

            try
            {
                File.Copy(e.FullPath, destFile);
            }
            catch (Exception ex)
            {
                RichTextBoxExtensions.AppendText(richTextBox1, $"复制失败: {ex.Message}", Color.Red);
                richTextBox1.AppendText(Environment.NewLine);
            }
        }));
    });
}

方案3:排除自身操作的触发

若Watcher监控了子目录,判断当前触发的文件是否是自己复制的文件,直接跳过:

private void OnCreated(object sender, FileSystemEventArgs e)
{
    this.Invoke(new Action(delegate ()
    {
        // 跳过备份目录内的文件触发的事件
        if (e.FullPath.StartsWith(Path.Combine(savedGamesPath, "Save Game "), StringComparison.OrdinalIgnoreCase))
            return;

        // 后续处理代码...
    }));
}

额外优化建议

  • 始终用Path.Combine拼接路径,避免手动拼接带来的分隔符错误。
  • Directory.CreateDirectory无需提前判断Directory.Exists,该方法会自动处理目录已存在的情况。
  • 添加异常捕获逻辑,避免单个文件处理失败导致整个Watcher逻辑崩溃。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 21:54:24