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

如何确保从网络驱动器复制的文件完成写入后再执行读取操作?

如何避免在文件完全复制完成前读取文件?

这个场景我太熟悉了——跨组件处理文件时,FileSystemWatcher经常会“迫不及待”触发Created事件,加上网络驱动器的缓存特性,很容易导致读取半写完的文件。你目前用的isFileLocked方法有个致命漏洞:它只尝试以只读方式打开文件,但很多写进程(包括PowerShell的Copy-Item)在写入时会允许读共享,这时候你的检测会误以为文件已经就绪,但实际上内容还在传输中。

下面给你几个从根源解决问题的方案,按可靠性排序:

1. 临时文件+原子重命名(首推方案)

这是业界公认最可靠的做法,核心思路是:先把文件复制到目标目录的临时文件,复制完成后再重命名为最终文件名。因为文件重命名是原子操作(操作系统保证要么完成要么不做),FileSystemWatcher只会在重命名完成后触发Created事件,这时候文件肯定是完整的。

PowerShell复制组件修改:

# 生成临时文件名(比如加.tmp后缀)
$tmpFileName = $currentfile.Name + ".tmp"
$tmpDestination = Join-Path $destfolder $tmpFileName

# 先复制到临时文件
Copy-Item $currentfile.FullName -Destination $tmpDestination –Force -ErrorAction Stop

# 复制完成后,原子重命名为最终文件
Rename-Item $tmpDestination -Destination (Join-Path $destfolder $currentfile.Name) -Force -ErrorAction Stop

C#监控组件配合:

可以给FileSystemWatcher设置过滤规则,忽略.tmp后缀的文件:

fileSystemWatcher.Filter = "*.*"; // 或者更具体的后缀
fileSystemWatcher.Created += (sender, e) => {
    if (Path.GetExtension(e.FullPath).Equals(".tmp", StringComparison.OrdinalIgnoreCase))
    {
        // 跳过临时文件
        return;
    }
    // 处理正式文件
    ProcessFile(e.FullPath);
};

这个方案完全避免了半写文件被读取的可能,唯一的代价是需要修改两边的代码,但绝对值得。

2. 改进文件就绪检测逻辑

如果你暂时不想修改复制流程,可以强化isFileLocked的检测逻辑,不仅检查文件是否被锁定,还要验证文件大小是否稳定(避免网络缓存导致的“假存在”):

private bool IsFileFullyWritten(string filePath)
{
    if (!File.Exists(filePath))
        return false;

    FileInfo fileInfo = new FileInfo(filePath);
    long initialSize = fileInfo.Length;

    // 先尝试以独占方式打开文件(确保没有写入进程占用)
    try
    {
        using (FileStream fs = fileInfo.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None))
        {
            // 打开成功,说明文件没有被锁定
        }
    }
    catch (IOException)
    {
        return false;
    }

    // 再检查文件大小是否稳定(应对网络缓存延迟)
    Thread.Sleep(100); // 等待100毫秒,可根据文件大小调整
    return fileInfo.Length == initialSize;
}

调用这个方法时,建议循环等待几次(比如最多等5秒),如果始终返回false就放弃或者记录日志:

int retryCount = 0;
while (retryCount < 50) // 最多等5秒(50*100ms)
{
    if (IsFileFullyWritten(filePath))
        break;
    Thread.Sleep(100);
    retryCount++;
}

if (retryCount >= 50)
{
    // 处理文件无法就绪的情况,比如记录日志、跳过
    return;
}

// 开始读取文件

3. 调整FileSystemWatcher的通知规则

默认的FileSystemWatcher会监控很多文件属性变化(比如LastWrite时间),有时候文件还在写入,但LastWrite已经更新,导致Created事件提前触发。你可以缩小监控范围,只关注文件名变化,或者结合Size属性:

fileSystemWatcher.NotifyFilter = NotifyFilter.FileName | NotifyFilter.Size;

不过这个只是辅助手段,不能完全替代前面的方案,因为网络文件系统的缓存可能还是会导致Size属性提前更新。

为什么你的原方法会失效?

你用的File.OpenRead会请求FileShare.ReadWrite权限,而PowerShell的Copy-Item在写入时默认允许读共享,所以即使文件还在被写入,OpenRead也能成功,导致你的方法误判文件已就绪。而用FileAccess.ReadWrite + FileShare.None的方式,会强制检查是否有其他进程在写入,更准确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 19:59:06