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

如何让FileSystemWatcher在本地/远程文件复制完成后启动处理?

如何让FileSystemWatcher等待文件复制完成后再处理?

我写了一个程序,用StartFileWatcher监控C:\TEMP文件夹(包含子目录),根据文件的LastWriteTimeUtc把文件信息写入C# Channel,并做排序对比。多数情况下(文件快速复制完成时),文件能按LastWriteTime顺序处理,但如果复制耗时较长,顺序就会出错。目前我用手动设置延迟(await Task.Delay(5000, cancellationToken);)来处理,但如果复制超过5000ms就失效了。有没有办法不用手动延迟,让FileSystemWatcher等文件复制完成后再开始工作?

Main:

static async Task Main(string[] args)
{
    string folderPath = @"C:\TEMP";
    CancellationTokenSource cts = new CancellationTokenSource();

    Channel<string> fileChannel = Channel.CreateUnbounded<string>();

    // Start the file watcher
    Task watcherTask = StartFileWatcher(
                folderPath, fileChannel.Writer, cts.Token);

    // Start processing files
    Task processingTask = ProcessFilesAsync(
                fileChannel.Reader, cts.Token);

    // Wait for user input to stop
    Console.WriteLine("Press Enter to stop...");
    Console.ReadLine();

    // Cancel the tasks
    cts.Cancel();

    // Wait for the tasks to complete
    await Task.WhenAll(watcherTask, processingTask);

    Console.WriteLine("Program stopped.");
}

StartFileWatcher:

static async Task StartFileWatcher(
            string folderPath, 
            ChannelWriter<string> channelWriter, 
            CancellationToken cancellationToken)
{
    using (var watcher = new FileSystemWatcher(folderPath))
    {
        List<string> pendingFiles = new List<string>();

        watcher.IncludeSubdirectories = true;
        watcher.Created += (sender, e) =>
        {
            if (!e.Name.StartsWith("~"))
            {
                lock (pendingFiles)
                {
                    pendingFiles.Add(e.FullPath);
                    pendingFiles.Sort((a, b) =>
                        File.GetLastWriteTimeUtc(a)
                                    .CompareTo(File.GetLastWriteTimeUtc(b)));
                }
            }
        };

        watcher.EnableRaisingEvents = true;

        while (!cancellationToken.IsCancellationRequested)
        {
            List<string> filesToProcess;

            lock (pendingFiles)
            {
                filesToProcess = new List<string>(pendingFiles);
                pendingFiles.Clear();
            }

            foreach (var filePath in filesToProcess)
            {
                channelWriter.TryWrite(filePath);
            }

            // Adjust the delay as needed
            await Task.Delay(5000, cancellationToken);
        }
    }
}

ProcessFilesAsync:

static async Task ProcessFilesAsync(
        ChannelReader<string> channelReader, 
        CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            if (await channelReader.WaitToReadAsync(cancellationToken))
            {
                while (channelReader.TryRead(out string filePath))
                {
                    Console.WriteLine($"file: {filePath}," + 
                                      $"LastWriteTime: " +
                                      $"{new FileInfo(filePath).LastWriteTime}");
                    // Simulate processing delay
                    await Task.Delay(2000, cancellationToken);
                }
            }
        }
    }

解决方案:检测文件锁定状态判断复制完成

FileSystemWatcher触发Created事件时,文件往往还处于写入/复制状态,此时读取LastWriteTime会不准确,直接处理也可能引发错误。可以通过尝试独占打开文件的方式,判断文件是否已完成写入,替代固定延迟方案。

修改后的核心逻辑:

  1. 新增待检测队列,临时存放刚创建的文件
  2. 定期检测队列中的文件,尝试独占打开验证是否解锁
  3. 解锁成功的文件加入排序队列,再写入Channel处理

修改后的StartFileWatcher代码:

static async Task StartFileWatcher(
            string folderPath, 
            ChannelWriter<string> channelWriter, 
            CancellationToken cancellationToken)
{
    using (var watcher = new FileSystemWatcher(folderPath))
    {
        // 线程安全的待检测文件队列
        ConcurrentQueue<string> pendingCheckQueue = new ConcurrentQueue<string>();
        // 已就绪的待处理文件列表(用于排序)
        List<string> pendingProcessList = new List<string>();

        watcher.IncludeSubdirectories = true;
        watcher.Created += (sender, e) =>
        {
            if (!e.Name.StartsWith("~"))
            {
                pendingCheckQueue.Enqueue(e.FullPath);
            }
        };

        watcher.EnableRaisingEvents = true;

        while (!cancellationToken.IsCancellationRequested)
        {
            // 批量检测待验证文件
            while (pendingCheckQueue.TryDequeue(out string filePath))
            {
                if (IsFileReady(filePath))
                {
                    lock (pendingProcessList)
                    {
                        pendingProcessList.Add(filePath);
                        // 按LastWriteTimeUtc排序
                        pendingProcessList.Sort((a, b) =>
                            File.GetLastWriteTimeUtc(a).CompareTo(File.GetLastWriteTimeUtc(b)));
                    }
                }
                else
                {
                    // 文件仍锁定,放回队列下次检测
                    pendingCheckQueue.Enqueue(filePath);
                }
            }

            // 处理已就绪的文件
            List<string> filesToProcess;
            lock (pendingProcessList)
            {
                filesToProcess = new List<string>(pendingProcessList);
                pendingProcessList.Clear();
            }

            foreach (var filePath in filesToProcess)
            {
                channelWriter.TryWrite(filePath);
            }

            // 短间隔轮询,平衡响应速度与资源占用
            await Task.Delay(100, cancellationToken);
        }
    }
}

// 辅助方法:验证文件是否可独占访问(判断写入完成)
private static bool IsFileReady(string filePath)
{
    try
    {
        // 尝试以独占方式打开文件
        using (FileStream stream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
        {
            return true;
        }
    }
    catch (IOException)
    {
        // 文件被锁定,说明还在写入
        return false;
    }
    catch (Exception)
    {
        // 文件不存在或其他异常,直接排除
        return false;
    }
}

优化说明:

  • 用ConcurrentQueue存储待检测文件,避免线程安全问题,无需额外加锁
  • 轮询间隔缩短至100ms,既保证响应及时性,又不会占用过多系统资源
  • 锁定的文件会重新放回队列,直到解锁或任务被取消,完全适配任意时长的复制操作

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 12:42:04