【问题标题】:Wait Until File Is Completely Written等到文件完全写入
【发布时间】:2012-06-14 11:25:38
【问题描述】:

在一个目录中创建文件 (FileSystemWatcher_Created) 时,我将其复制到另一个目录。但是当我创建一个大 (>10MB) 文件时,它无法复制文件,因为它已经开始复制,而文件尚未完成创建...
这会导致无法复制该文件,因为它已被另一个进程使用。 ;(
有什么帮助吗?

class Program
{
    static void Main(string[] args)
    {
        string path = @"D:\levan\FolderListenerTest\ListenedFolder";
        FileSystemWatcher listener; 
        listener = new FileSystemWatcher(path);
        listener.Created += new FileSystemEventHandler(listener_Created);
        listener.EnableRaisingEvents = true;

        while (Console.ReadLine() != "exit") ;
    }

    public static void listener_Created(object sender, FileSystemEventArgs e)
    {
        Console.WriteLine
                (
                    "File Created:\n"
                   + "ChangeType: " + e.ChangeType
                   + "\nName: " + e.Name
                   + "\nFullPath: " + e.FullPath
                );
        File.Copy(e.FullPath, @"D:\levan\FolderListenerTest\CopiedFilesFolder\" + e.Name);
        Console.Read();
    }
}

【问题讨论】:

标签: c# file io filesystems copy


【解决方案1】:

您面临的问题只有解决方法。

在开始复制过程之前检查文件ID是否正在处理中。您可以调用以下函数,直到获得 False 值。

第一种方法,直接复制自this answer

private bool IsFileLocked(FileInfo file)
{
    FileStream stream = null;

    try
    {
        stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
    }
    catch (IOException)
    {
        //the file is unavailable because it is:
        //still being written to
        //or being processed by another thread
        //or does not exist (has already been processed)
        return true;
    }
    finally
    {
        if (stream != null)
            stream.Close();
    }

    //file is not locked
    return false;
}

第二种方法:

const int ERROR_SHARING_VIOLATION = 32;
const int ERROR_LOCK_VIOLATION = 33;
private bool IsFileLocked(string file)
{
    //check that problem is not in destination file
    if (File.Exists(file) == true)
    {
        FileStream stream = null;
        try
        {
            stream = File.Open(file, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
        }
        catch (Exception ex2)
        {
            //_log.WriteLog(ex2, "Error in checking whether file is locked " + file);
            int errorCode = Marshal.GetHRForException(ex2) & ((1 << 16) - 1);
            if ((ex2 is IOException) && (errorCode == ERROR_SHARING_VIOLATION || errorCode == ERROR_LOCK_VIOLATION))
            {
                return true;
            }
        }
        finally
        {
            if (stream != null)
                stream.Close();
        }
    }
    return false;
}

【讨论】:

  • 你忘了复制常量值(ERROR_SHARING_VIOLATION = 32, ERROR_LOCK_VIOLATION = 33)
  • @RomilKumarJain 我们可以看看这个方法的用法吗?
  • @Roxy'Pro 你说的用法是什么意思?
【解决方案2】:

来自FileSystemWatcher 的文档:

创建文件后立即引发OnCreated 事件。如果一个文件 正在被复制或传输到监视目录中, OnCreated 事件将立即引发,然后是一个或多个 OnChanged 事件。

因此,如果复制失败(捕获异常),请将其添加到仍需要移动的文件列表中,并在 OnChanged 事件期间尝试复制。最终,它应该会起作用。

类似(不完整;捕获特定异常、初始化变量等):

public static void listener_Created(object sender, FileSystemEventArgs e)
{
    Console.WriteLine
            (
                "File Created:\n"
               + "ChangeType: " + e.ChangeType
               + "\nName: " + e.Name
               + "\nFullPath: " + e.FullPath
            );
    try {
        File.Copy(e.FullPath, @"D:\levani\FolderListenerTest\CopiedFilesFolder\" + e.Name);
    }
    catch {
        _waitingForClose.Add(e.FullPath);
    }
    Console.Read();
}

public static void listener_Changed(object sender, FileSystemEventArgs e)
{
     if (_waitingForClose.Contains(e.FullPath))
     {
          try {
              File.Copy(...);
              _waitingForClose.Remove(e.FullPath);
          }
          catch {}
     }

}

【讨论】:

  • 这是一个很好的方法,但我马上需要它,所以我已经将 IsFileReady 放入 while 并且它现在可以工作了
【解决方案3】:

这是一个旧线程,但我会为其他人添加一些信息。

我在编写 PDF 文件的程序时遇到了类似的问题,有时它们需要 30 秒才能呈现......这与我的 watcher_FileCreated 类在复制文件之前等待的时间相同。

文件未锁定。

在这种情况下,我检查了 PDF 的大小,然后在比较新大小之前等待 2 秒,如果它们不相等,线程将休眠 30 秒并重试。

【讨论】:

    【解决方案4】:

    你真的很幸运 - 编写文件的程序将其锁定,因此你无法打开它。如果它没有锁定它,你会复制一个部分文件,而不知道有问题。

    当您无法访问某个文件时,您可以假设它仍在使用中(更好的是 - 尝试以独占模式打开它,看看当前是否有其他人正在打开它,而不是猜测 File.复制)。如果文件被锁定,您将不得不在其他时间复制它。如果它没有被锁定,你可以复制它(这里有轻微的竞争条件)。

    “其他时间”是什么时候?我不记得 FileSystemWatcher 何时为每个文件发送多个事件 - 检查一下,您可能只需忽略该事件并等待另一个事件就足够了。如果没有,您可以随时设置时间并在 5 秒内重新检查文件。

    【讨论】:

      【解决方案5】:

      你自己已经给出了答案;您必须等待文件的创建完成。一种方法是检查文件是否仍在使用中。可以在这里找到一个例子:Is there a way to check if a file is in use?

      请注意,您必须修改此代码才能使其在您的情况下工作。您可能想要类似(伪代码):

      public static void listener_Created()
      {
         while CheckFileInUse()
            wait 1000 milliseconds
      
         CopyFile()
      }
      

      显然,您应该保护自己免受无限 while 的影响,以防所有者应用程序永远不会释放锁。此外,可能值得查看您可以订阅的FileSystemWatcher 中的其他事件。可能有一个事件可以用来规避整个问题。

      【讨论】:

        【解决方案6】:

        当文件以二进制(逐字节)写入时,创建 FileStream 及以上解决方案不起作用,因为文件已准备好并写入每个字节,所以在这种情况下,您需要其他解决方法,如下所示: 在创建文件或要开始处理文件时执行此操作

        long fileSize = 0;
        currentFile = new FileInfo(path);
        
        while (fileSize < currentFile.Length)//check size is stable or increased
        {
          fileSize = currentFile.Length;//get current size
          System.Threading.Thread.Sleep(500);//wait a moment for processing copy
          currentFile.Refresh();//refresh length value
        }
        
        //Now file is ready for any process!
        

        【讨论】:

          【解决方案7】:

          因此,在快速浏览了其中一些和其他类似问题后,我今天下午进行了一场快乐的追逐,试图解决两个独立程序使用文件作为同步(以及文件保存)方法的问题。有点不寻常的情况,但它确实突出了“检查文件是否被锁定,然后打开它”方法的问题。

          问题在于:在您检查文件和实际打开文件之间,文件可能变为锁定。如果您不查找它,则很难找到零星的无法复制文件,因为它已被另一个进程使用错误。

          基本解决方法是尝试在 catch 块中打开文件,这样如果它被锁定,您可以重试。这样,检查和打开之间没有经过的时间,操作系统同时进行。

          此处的代码使用 File.Copy,但它也适用于 File 类的任何静态方法:File.Open、File.ReadAllText、File.WriteAllText 等。

          /// <param name="timeout">how long to keep trying in milliseconds</param>
          static void safeCopy(string src, string dst, int timeout)
          {
              while (timeout > 0)
              {
                  try
                  {
                      File.Copy(src, dst);
          
                      //don't forget to either return from the function or break out fo the while loop
                      break;
                  }
                  catch (IOException)
                  {
                      //you could do the sleep in here, but its probably a good idea to exit the error handler as soon as possible
                  }
                  Thread.Sleep(100);
          
                  //if its a very long wait this will acumulate very small errors. 
                  //For most things it's probably fine, but if you need precision over a long time span, consider
                  //   using some sort of timer or DateTime.Now as a better alternative
                  timeout -= 100;
              }
          }
          

          关于并行性的另一个小提示: 这是一个同步方法,它会在等待和处理线程时阻塞它的线程。这是最简单的方法,但如果文件长时间保持锁定状态,您的程序可能会变得无响应。并行化是一个太大的话题,无法在此处深入探讨,(您可以设置异步读/写的方法数量有点荒谬),但这是可以并行化的一种方式。

          public class FileEx
          {
              public static async void CopyWaitAsync(string src, string dst, int timeout, Action doWhenDone)
              {
                  while (timeout > 0)
                  {
                      try
                      {
                          File.Copy(src, dst);
                          doWhenDone();
                          break;
                      }
                      catch (IOException) { }
          
                      await Task.Delay(100);
                      timeout -= 100;
                  }
              }
          
              public static async Task<string> ReadAllTextWaitAsync(string filePath, int timeout)
              {
                  while (timeout > 0)
                  {
                      try {
                          return File.ReadAllText(filePath);
                      }
                      catch (IOException) { }
          
                      await Task.Delay(100);
                      timeout -= 100;
                  }
                  return "";
              }
          
              public static async void WriteAllTextWaitAsync(string filePath, string contents, int timeout)
              {
                  while (timeout > 0)
                  {
                      try
                      {
                          File.WriteAllText(filePath, contents);
                          return;
                      }
                      catch (IOException) { }
          
                      await Task.Delay(100);
                      timeout -= 100;
                  }
              }
          }
          

          下面是它的使用方法:

          public static void Main()
          {
              test_FileEx();
              Console.WriteLine("Me First!");
          }    
          
          public static async void test_FileEx()
          {
              await Task.Delay(1);
          
              //you can do this, but it gives a compiler warning because it can potentially return immediately without finishing the copy
              //As a side note, if the file is not locked this will not return until the copy operation completes. Async functions run synchronously
              //until the first 'await'. See the documentation for async: https://msdn.microsoft.com/en-us/library/hh156513.aspx
              CopyWaitAsync("file1.txt", "file1.bat", 1000);
          
              //this is the normal way of using this kind of async function. Execution of the following lines will always occur AFTER the copy finishes
              await CopyWaitAsync("file1.txt", "file1.readme", 1000);
              Console.WriteLine("file1.txt copied to file1.readme");
          
              //The following line doesn't cause a compiler error, but it doesn't make any sense either.
              ReadAllTextWaitAsync("file1.readme", 1000);
          
              //To get the return value of the function, you have to use this function with the await keyword
              string text = await ReadAllTextWaitAsync("file1.readme", 1000);
              Console.WriteLine("file1.readme says: " + text);
          }
          
          //Output:
          //Me First!
          //file1.txt copied to file1.readme
          //file1.readme says: Text to be duplicated!
          

          【讨论】:

            【解决方案8】:

            您可以使用以下代码检查文件是否可以以独占访问权限打开(即,它没有被其他应用程序打开)。如果文件没有关闭,您可以稍等片刻再次检查,直到文件关闭并且您可以安全地复制它。

            您仍应检查 File.Copy 是否失败,因为其他应用程序可能会在您检查文件和复制文件之间打开该文件。

            public static bool IsFileClosed(string filename)
            {
                try
                {
                    using (var inputStream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.None))
                    {
                        return true;
                    }
                }
                catch (IOException)
                {
                    return false;
                }
            }
            

            【讨论】:

            【解决方案9】:

            我想在这里添加一个答案,因为这对我有用。我使用了时间延迟,while 循环,我能想到的一切。

            我打开了输出文件夹的 Windows 资源管理器窗口。我关闭了它,一切都像魅力一样运作。

            我希望这对某人有所帮助。

            【讨论】:

            • 所以如果用户打开该目录,您的应用程序会崩溃?
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2015-01-30
            • 1970-01-01
            • 2020-11-22
            • 2021-01-25
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多