如何等到文件从网络驱动器成功复制后再读取?

Posted

技术标签:

【中文标题】如何等到文件从网络驱动器成功复制后再读取?【英文标题】:How to wait until a file is successfully copied from a network drive before reading from it? 【发布时间】:2021-12-06 07:06:39 【问题描述】:

我目前面临一个由多个组件组成的应用程序的问题。 该应用程序的一个组件会定期检查网络驱动器上的新文件并将它们复制到本地文件夹中。该应用程序的另一个组件使用FileSystemWatcher 来监视本地文件夹中的任何新文件。如果复制了新文件,则调用FileSystemWatcherCreated 事件,然后应用程序将读取文件内容并将文件导入数据库。 为了防止应用程序在文件完全复制到本地文件夹之前尝试读取文件,它会定期调用以下函数,直到它返回false

private bool isFileLocked(string filePath)

    try
    
        if (!File.Exists(filePath))
        
            return false;
        

        using (FileStream fs = File.OpenRead(filePath))
        
        

        return false;
    
    catch (IOException)
    
        return true;
    

不幸的是,这似乎不适用于所有情况。有时,我注意到文件在完全写入本地文件夹之前已被读取。发生这种情况时,尝试复制文件的组件会收到以下错误:

System.IO.IOException: The process cannot access the file '...' because it is being used by another process.

复制文件的组件是用 PowerShell 编写的,并使用以下 Cmdlet 进行复制:

Copy-Item $currentfile.FullName -Destination "$destfolder" –Force -ErrorAction Stop

使用FileSystemWatcher 并导入文件的组件是基于C# 的Windows 服务。 在文件完全复制到本地文件夹之前,如何防止它读取文件?

【问题讨论】:

如何让你需要等待的方法“异步”?用asyncawait标记他们的操作一一对应。如果线程被阻塞也没有影响,请将它们设为任务并调用Task.Wait 以确保它已完成。 如果您尝试打开文件进行写入有什么不同吗? @MatthewWatson 我不知道。这个问题很少发生(有时一两天都不会发生),所以我不能在短时间内轻松测试任何东西。 为了提高可靠性,我会尝试实施某种“事务性”方案。一个简单的方法是复制具有临时目标名称的文件,例如$currentFile.FullName + '.tmp'。复制文件后,将其重命名为最终名称。当使用FileSystemWatcher的组件只关注最终名称(忽略“*.tmp”)时,可以确定文件已被完整复制。 @marsze 通过调用 isFileLocked 来完成等待,直到它返回 false。只有在这种情况下,代码才会读取文件内容。我认为这里可能发生的情况是,在复制文件时创建和打开文件之间可能有很短的时间,所以如果在创建和打开之间调用 isFileLocked ,它将“窃取”文件访问权限并阻止复制从写入文件内容。这有意义吗? 【参考方案1】:

如果您不担心一点延迟 - 它可能会解决您的问题:

static void Main(string[] args)

    FileSystemWatcher fsw = new FileSystemWatcher("SomePathToFolder");
    fsw.EnableRaisingEvents = true;
    fsw.Created += async (s, a) =>
    
        while (FileIsLocked(a.FullPath))
        
            Console.WriteLine($"File a.Name is locked!");
            await Task.Delay(TimeSpan.FromSeconds(5)); // 5 seconds delay between checks
        
  
        Console.WriteLine($"File a.Name available!");
  
        // You can put here another delay to be 102% sure that file is free,
        // but I suppose this is too much.
        using (FileStream fs = File.OpenRead(a.FullPath))
        
            Console.WriteLine($"File a.Name opened for reading.");
            // Do what you need
            await Task.Run(() => ImportFileToDatabase(fs));
        

        Console.WriteLine($"File a.Name closed.");
    ;


    Console.ReadKey();


static bool FileIsLocked(string filePath)

    if (!File.Exists(filePath))
        return false;

    try
    
        using (FileStream fs = File.OpenRead(filePath))  
        return false;
    
    catch  

    return true;

【讨论】:

我是否正确,这基本上与我已经在做的事情相同,但是在检查文件是否可访问和最终尝试从文件中读取之前进行额外检查之间有更多延迟,或者我是缺少什么? 是的,罪魁祸首只是延迟。如果在复制之后的文件处理之前有一些延迟并不重要(而不是即时(立即)文件处理) - 你可以玩延迟。另请注意@marsze 的回答,它将文件打开和等待打开以一种异步方法结合在一起。 是的,我已经在考虑这样的事情,以防万一没有更好的解决方案,但我仍然觉得它更像是一种解决方法,而不是实际的解决方案。虽然这可能会消除 99% 的文件在完全复制之前被读取的情况,但我仍然不知道它是否真正解决了问题。我实施的现有检查不应该已经阻止了这个问题的发生吗? 102% 确定 - 您应该删除 PowerShell 复制并将其放入 C#。使用流,您将能够监控并完全控制复制。【参考方案2】:

一些解决方案建议here。我在使用FileSystemWatcher 时遇到过类似的问题。这是我使用的(简化):

async Task<FileStream> OpenWaitAsync(string path, TimeSpan interval, CancellationToken cancellationToken = default)

    const int ERROR_SHARING_VIOLATION = unchecked((int)0x80070020);
    while (true)
    
        try
        
            return File.OpenRead(path);
        
        catch (IOException ioe) when (ioe.HResult == ERROR_SHARING_VIOLATION)
        
            await Task.Delay(interval, cancellationToken);
        
    

【讨论】:

以上是关于如何等到文件从网络驱动器成功复制后再读取?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Python 将文件复制到网络路径或驱动器

ensp云朵检测不到环回适配器

如何让VMware Workstation和自己的硬盘相互访问文件?

批处理:从映射的网络驱动器以管理员身份运行

将 URL 回显到网络驱动器上名称中带有空格的文件

在网络驱动器上搜索 .msg 文件以查找关键字