FileSystemWatcher - 文件可以使用

时间:2012-09-04 16:55:58

标签: c# .net filesystemwatcher

将文件复制到文件观察程序文件夹时,如何识别文件是否已完全复制并可以使用?因为我在文件复制期间收到多个事件。 (使用File.Copy通过另一个程序复制该文件。)

8 个答案:

答案 0 :(得分:12)

当我遇到这个问题时,我提出的最佳解决方案是不断尝试获取文件的独占锁定;在写入文件时,锁定尝试将失败,基本上是this中的方法。一旦文件没有被写入,锁定将成功。

不幸的是,唯一的方法是在打开文件时包装一个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);
    }
}

答案 1 :(得分:2)

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

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

答案 2 :(得分:2)

这是一种方法,它将重试文件访问次数达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...
        }
    }
}

答案 3 :(得分:1)

注意:在一般情况下,此问题无法解决。如果没有关于文件使用的先验知识,您无法知道其他程序是否已完成对文件的操作。

在您的特定情况下,您应该能够找出File.Copy包含的操作。

在整个操作过程中,最可能的目标文件被锁定。在这种情况下,您应该能够简单地尝试打开文件并处理“共享模式违规”异常。

您也可以等待一段时间... - 非常不可靠的选项,但如果您知道文件的大小范围,您可以有合理的延迟让Copy完成。

您还可以“发明”某种交易系统 - 即创建另一个文件,如“destination_file_name.COPYLOCK”,复制文件在复制“destination_file_name”之前创建的程序,然后删除。

答案 4 :(得分:1)

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

答案 5 :(得分:1)

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

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

答案 6 :(得分:0)

    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;

...当然,您可以修改此方面以满足您的需求。

答案 7 :(得分:-1)

我已经通过两个功能解决了这个问题:

  1. 实现在这个问题中看到的 MemoryCache 模式:A robust solution for FileSystemWatcher firing events multiple times
  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;
}

TimeoutSeconds 是一个设置,您可以将设置放在任何位置。这可以根据您的环境进行调整。