14

将文件复制到文件监视文件夹时,如何确定文件是否已完全复制并准备好使用?因为我在文件复制期间收到了多个事件。(该文件是通过另一个程序使用 File.Copy 复制的。)

4

8 回答 8

12

当我遇到这个问题时,我想出的最佳解决方案是不断尝试对文件进行排他锁;在写入文件时,锁定尝试将失败,本质上是答案中的方法。一旦文件不再被写入,锁定就会成功。

不幸的是,这样做的唯一方法是在打开文件时使用 try/catch,这让我感到畏缩——不得不使用 try/catch 总是很痛苦。不过,似乎没有任何办法解决这个问题,所以这就是我最终使用的。

修改该答案中的代码就可以了,所以我最终使用了这样的东西:

private void WaitForFile(FileInfo file)
{
    FileStream stream = null;
    bool FileReady = false;
    while(!FileReady)
    {
        try
        {
            using(stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None)) 
            { 
                FileReady = true; 
            }
        }
        catch (IOException)
        {
            //File isn't ready yet, so we need to keep on waiting until it is.
        }
        //We'll want to wait a bit between polls, if the file isn't ready.
        if(!FileReady) Thread.Sleep(1000);
    }
}
于 2012-09-04T17:20:58.023 回答
3

这是一种方法,它将重试文件访问最多 X 次,Sleep尝试之间有一次。如果它永远无法访问,则应用程序继续:

private static bool GetIdleFile(string path)
{
    var fileIdle = false;
    const int MaximumAttemptsAllowed = 30;
    var attemptsMade = 0;

    while (!fileIdle && attemptsMade <= MaximumAttemptsAllowed)
    {
        try
        {
            using (File.Open(path, FileMode.Open, FileAccess.ReadWrite))
            {
                fileIdle = true;
            }
        }
        catch
        {
            attemptsMade++;
            Thread.Sleep(100);
        }
    }

    return fileIdle;
}

它可以这样使用:

private void WatcherOnCreated(object sender, FileSystemEventArgs e)
{
    if (GetIdleFile(e.FullPath))
    {
        // Do something like...
        foreach (var line in File.ReadAllLines(e.FullPath))
        {
            // Do more...
        }
    }
}
于 2014-01-10T19:41:17.930 回答
2

Note: this problem is not solvable in generic case. Without prior knowledge about file usage you can't know if other program(s) finished operation with the file.

In your particular case you should be able to figure out what operations File.Copy consist of.

Most likely destination file is locked during whole operation. In this case you should be able to simply try to open file and handle "sharing mode violation" exception.

You can also wait for some time... - very unreliable option, but if you know size range of files you may be able to have reasonable delay to let Copy to finish.

You can also "invent" some sort of transaction system - i.e. create another file like "destination_file_name.COPYLOCK" which program that copies file would create before copying "destination_file_name" and delete afterward.

于 2012-09-04T17:13:30.853 回答
2

我在写文件时遇到了这个问题。在文件完全写入和关闭之前,我收到了事件。

解决方案是使用临时文件名并在完成后重命名文件。然后注意文件重命名事件而不是文件创建或更改事件。

于 2012-09-04T17:03:53.527 回答
1
    private Stream ReadWhenAvailable(FileInfo finfo, TimeSpan? ts = null) => Task.Run(() =>
    {
        ts = ts == null ? new TimeSpan(long.MaxValue) : ts;
        var start = DateTime.Now;
        while (DateTime.Now - start < ts)
        {
            Thread.Sleep(200);
            try
            {
                return new FileStream(finfo.FullName, FileMode.Open);
            }
            catch { }
        }
        return null;
    })
    .Result;

...当然,您可以修改它的各个方面以满足您的需要。

于 2015-11-17T15:31:27.697 回答
1

一种可能的解决方案(在我的情况下有效)是使用 Change 事件。您可以使用刚刚创建的文件的名称登录创建事件,然后捕获更改事件并验证文件是否刚刚创建。当我在更改事件中操作文件时,它没有向我抛出错误“文件正在使用中”

于 2020-07-04T03:33:44.193 回答
1

如果您像我一样进行某种进程间通信,您可能需要考虑以下解决方案:

  1. App A 写入您感兴趣的文件,例如“Data.csv”
  2. 完成后,应用程序 A 写入第二个文件,例如。“数据。确认”
  3. 在您的 C# 应用程序中,让 FileWatcher 监听“*.confirmed”文件。当您收到此事件时,您可以安全地阅读“Data.csv”,因为它已由应用 A 完成。
于 2021-07-26T15:32:31.883 回答
-1

我用两个功能解决了这个问题:

  1. 实现MemoryCache这个问题中看到的模式:FileSystemWatcher 多次触发事件的强大解决方案
  2. 实现一个访问超时的 try\catch 循环

您需要收集环境中的平均复制时间,并将内存缓存超时设置为至少与新文件的最短锁定时间一样长。这消除了您的处理指令中的重复,并允许一些时间来完成复制。您在第一次尝试时将获得更好的成功,这意味着在 try\catch 循环中花费的时间更少。

这是 try\catch 循环的示例:

public static IEnumerable<string> GetFileLines(string theFile)
{
    DateTime startTime = DateTime.Now;
    TimeSpan timeOut = TimeSpan.FromSeconds(TimeoutSeconds);
    TimeSpan timePassed;
    do
    {
        try
        {
            return File.ReadLines(theFile);
        }
        catch (FileNotFoundException ex)
        {
            EventLog.WriteEntry(ProgramName, "File not found: " + theFile, EventLogEntryType.Warning, ex.HResult);
            return null;
        }
        catch (PathTooLongException ex)
        {
            EventLog.WriteEntry(ProgramName, "Path too long: " + theFile, EventLogEntryType.Warning, ex.HResult);
            return null;
        }
        catch (DirectoryNotFoundException ex)
        {
            EventLog.WriteEntry(ProgramName, "Directory not found: " + theFile, EventLogEntryType.Warning, ex.HResult);
            return null;
        }
        catch (Exception ex)
        {
            // We swallow all other exceptions here so we can try again
            EventLog.WriteEntry(ProgramName, ex.Message, EventLogEntryType.Warning, ex.HResult);
        }

        Task.Delay(777).Wait();
        timePassed = DateTime.Now.Subtract(startTime);
    }
    while (timePassed < timeOut);

    EventLog.WriteEntry(ProgramName, "Timeout after waiting " + timePassed.ToString() + " seconds to read " + theFile, EventLogEntryType.Warning, 258);
    return null;
}

WhereTimeoutSeconds是一个设置,你可以把你的设置放在哪里。这可以针对您的环境进行调整。

于 2021-07-27T17:34:26.810 回答