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

C# FileSystemWatcher复制文件时文件被其他进程占用如何解决

问题核心原因

你遇到的占用异常不是自身代码未释放文件资源导致的,三个核心诱因:

  • FileSystemWatcher的Changed事件触发时机是目标文件开始写入时,而非写入完成时,此时写入文件的进程(比如文本编辑器、游戏存盘进程)持有文件锁,默认的File.Copy方法申请文件访问权限时会直接被系统拒绝。
  • 原生File.Copy默认以禁止其他进程写入的共享模式打开源文件,只要源文件被其他进程以写入模式打开,就会直接抛出IO异常,没有内置重试、兼容共享访问的逻辑。
  • 你当前代码里的全局变量makeonce没有做线程安全保护,FileSystemWatcher的事件由线程池触发,多事件并发时会出现目录重复创建、目标路径被覆盖的问题。

注意:你没有权限"释放其他进程持有的文件锁",这类锁归属持有文件句柄的外部进程,强行通过系统API关闭其他进程的句柄会导致对方数据损坏、进程崩溃,绝对不要用这类方案。

可直接落地的修复方案

核心思路是:改用支持共享读写的文件流手动复制,搭配短间隔重试机制,在文件可访问时持续覆盖写入目标位置,同时修复线程安全问题。

  • 移除原有的无保护全局布尔值判断,加线程锁保证目标目录只创建一次
  • 放弃原生File.Copy,手动创建文件流时指定FileShare.ReadWrite标记,允许源文件被其他进程同时读写,从根源避开大部分占用锁问题
  • 加入重试逻辑,遇到暂时的占用问题时等待几百毫秒再尝试,直到达到最大重试次数
  • 复制逻辑全程不持有长期锁,复制完成立刻释放流资源

修正后的代码实现如下:

// 根据实际监听的文件大小调整参数
private const int MaxRetryTimes = 15;
private const int RetryWaitMs = 300;
private readonly object _pathInitLock = new object();
private string _targetSavePath = string.Empty;

private void CopyFileOnChanged(string sourceDir, string sourceFullPath)
{
    // 保证目标目录只初始化一次,避免并发问题
    lock (_pathInitLock)
    {
        if (string.IsNullOrEmpty(_targetSavePath))
        {
            string dateStr = DateTime.Now.ToString("ddd MM.dd.yyyy");
            string timeStr = DateTime.Now.ToString("HH.mm tt");
            string saveFolderName = string.Format("Save Game {0} {1}", dateStr, timeStr);
            string saveFolderFullPath = Path.Combine(sourceDir, saveFolderName);
            Directory.CreateDirectory(saveFolderFullPath);
            string sourceFileName = Path.GetFileName(sourceFullPath);
            _targetSavePath = Path.Combine(saveFolderFullPath, sourceFileName);
        }
    }

    // 带重试的复制逻辑
    for (int retryCount = 0; retryCount < MaxRetryTimes; retryCount++)
    {
        try
        {
            // 核心:以共享读写模式打开源文件,允许其他进程同时写入
            using (FileStream sourceStream = new FileStream(
                path: sourceFullPath,
                mode: FileMode.Open,
                access: FileAccess.Read,
                share: FileShare.ReadWrite))
            using (FileStream targetStream = new FileStream(
                path: _targetSavePath,
                mode: FileMode.Create,
                access: FileAccess.Write,
                share: FileShare.None))
            {
                sourceStream.CopyTo(targetStream);
            }
            // 本次复制成功,直接返回
            return;
        }
        catch (IOException)
        {
            // 等待指定时间后重试
            Thread.Sleep(RetryWaitMs);
        }
    }
    // 超过最大重试次数输出日志,不抛出异常中断文件监听
    PrintErr($"文件 {sourceFullPath} 重试{MaxRetryTimes}次仍复制失败,可能被长期独占占用");
}
额外优化提示
  • 如果你监听的是大文件(比如GB级的游戏存档、视频文件),可以把MaxRetryTimes调到20以上,RetryWaitMs调到500毫秒,给写入进程留足完成写入的时间。
  • FileSystemWatcher对同一个文件的单次写入会触发多次Changed事件,这套重试覆盖逻辑天然兼容这个特性,每次触发都会把当前最新的文件内容覆盖到备份目录,完全符合你"文件变更完成前持续重复覆盖复制"的需求。
  • 如果需要监听多个文件的变更,不要用全局的_targetSavePath变量,改用字典按源文件路径存储对应的备份目标路径即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 09:46:02