【问题标题】:Safe stream update of file文件的安全流更新
【发布时间】:2008-11-27 21:22:07
【问题描述】:

我们通过将新记录写入临时文件来执行大型文本文件的更新,然后用临时文件替换旧文件。一个高度缩写的版本:

var tpath = Path.GetTempFileName();
try
{
    using (var sf = new StreamReader(sourcepath))
    using (var tf = new StreamWriter(tpath))
    {
        string line;
        while ((line = sf.ReadLine()) != null)
            tf.WriteLine(UpdateLine(line));
    }

    File.Delete(sourcepath);
    File.Move(tpath, sourcepath);
}
catch
{
    File.Delete(tpath);
    throw;
}

如果有任何东西抛出异常(找不到文件,没有权限),原始文件保持不变,这就是我们想要的。

但是,代码存在以下问题:

  1. 是否存在Delete 有效但Move 失败的实际情况?这将删除原始数据和更新数据。这会很糟糕。

  2. 最常见的故障是从另一个应用程序打开源文件,Delete 失败。这意味着所有更新工作都将被丢弃。有没有办法一开始就查看源文件是否可以删除,如果不可以就放弃更新?

  3. 我们有用户将 Windows 资源管理器摘要属性(如标题或评论)放在文件上。当我们删除文件时,这些将被丢弃。有没有办法将旧文件的摘要属性复制到新文件?我们应该这样做吗?

【问题讨论】:

    标签: .net windows


    【解决方案1】:

    避免“删除然后移动失败问题”的正常方法是:

    • 写入 file.new
    • 将 file.current 移至 file.old
    • 将 file.new 移动到 file.current
    • 删除文件.new

    那么当你来读的时候,如果file.current缺失就使用file.new,如果你看到file.old就删除它。

    检查文件是否可用:尝试打开它进行写入,但追加到末尾。当然,您需要先关闭手柄,然后再移动它,而中间其他人可以打开它——但这至少是一个合理的优化。

    我不确定是否要复制摘要等。

    【讨论】:

      【解决方案2】:

      为什么不先检查 FileAttributes 呢?

      试试这样的:

      //If File is readonly
      if ( (file.Attribute & System.FileAttributes.ReadOnly) == System.FileAttributes.ReadOnly ) 
              //Don't delete. 
      

      也可以尝试使用 .OpenWrite()。如果您可以打开要写入的文件,则说明该文件未被访问且当前未被使用。如果文件当前处于未打开状态,您只能打开文件进行写入。我不推荐这个,但它可能对你有帮助。

        FileStream fs = File.OpenWrite(file);
        fs.Close();
        return false; 
      

      您还可以使用 FileLock 检查方法。像这样的:

      protected virtual bool IsFileLocked(FileInfo file)
      {
          try
          {
              using (file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None))
              {
                 return false;
              }
          }
      
          catch (IOException)
          {
              return true;
          }
      

      }

      您可能还想检查 FileIOPermission.Write。这允许查看文件是否可写(并且能够删除)。

      fileIOPerm = New FileIOPermission(FileIOPermissionAccess.Write, FileSpec);
      fileIOPerm.Demand();
      

      关于原始帖子中的问题 #3...您始终可以使用 File.Copy(path1,path2,true) 将文件移动到临时文件夹。您可能需要考虑使用临时文件夹并为文件操作编写更好的逻辑。

      如果您确实决定使用临时文件夹或临时文件/中间文件,那么您也可以解决您的问题 #2。先尝试移动文件。

      【讨论】:

        【解决方案3】:

        一些“肮脏”的把戏。

        1. 首先不要删除原始文件,先将其移动到另一个位置(临时路径),然后如果移动更新文件成功,则删除旧文件。如果更新失败,您可以在某处恢复原始文件。

        2. 我认为这篇文章对你有帮助MSDN

        3. 如果用户需要“titles”和“cmets”,您应该保留它们。我从未尝试将它们从一个文件复制到另一个文件,所以我不知道如何为您提供帮助。

        【讨论】:

          【解决方案4】:

          Windows Vista 或更高版本上的Transactional NTFS 可能对您的方案有用。

          【讨论】:

            【解决方案5】:

            正如已经提到的,您确实应该研究 ReplaceFile,它旨在帮助您完成正在做的事情。 .NET 函数只是 Win32 函数的包装,人们可能希望原子性问题已经解决。

            【讨论】:

            • 不幸的是,如果源和目标位于不同的卷上,File.Replace 不起作用,这在我的情况下完全有可能。
            【解决方案6】:

            很多很好的建议。我能够通过以下方式解决问题:

            var sInfo = new FileInfo(sourcePath);
            if (sInfo.IsReadOnly)
                throw new IOException("File '" + sInfo.FullName + "' is read-only.");
            
            var tPath = Path.GetTempFileName();
            try
            {
                // This throws if sourcePath does not exist, is opened, or is not readable.
                using (var sf = sInfo.OpenText())
                using (var tf = new StreamWriter(tPath))
                {
                    string line;
                    while ((line = sf.ReadLine()) != null)
                        tf.WriteLine(UpdateLine(line));
                }
            
                string backupPath = sInfo.FullName + ".bak";
                if (File.Exists(backupPath))
                    File.Delete(backupPath);
            
                File.Move(tPath, backupPath);
                tPath = backupPath;
                File.Replace(tPath, sInfo.FullName, null);
            }
            catch (Exception ex)
            {
                File.Delete(tPath);
                throw new IOException("File '" + sInfo.FullName + "' could not be overwritten.", ex);
            }
            

            OpenText 如果源文件打开或不可读,则抛出,并且更新未完成。如果有任何问题,原始文件将保持不变。 Replace 将旧文件的摘要属性复制到新文件中。即使源文件与临时文件夹位于不同的卷上,这也有效。

            【讨论】:

              【解决方案7】:

              我发现将此模式包装在它自己的类中很有用。

              class Program {
                  static void Main( string[] args ) {
                      using( var ft = new FileTransaction( @"C:\MyDir\MyFile.txt" ) )
                      using( var sw = new StreamWriter( ft.TempPath ) ) {
                          sw.WriteLine( "Hello" );
                          ft.Commit();
                      }
                  }
              }
              
              public class FileTransaction :IDisposable {
                  public string TempPath { get; private set; }
                  private readonly string filePath;
              
                  public FileTransaction( string filePath ) {
                      this.filePath = filePath;
                      this.TempPath = Path.GetTempFileName();
                  }
              
                  public void Dispose() {
                      if( TempPath != null ) {
                          try {
                              File.Delete( TempPath );
                          }
                          catch { }
                      }
                  }
              
                  public void Commit() {
                      try {
                          var oldPath = filePath + ".old";
                          File.Move( filePath, oldPath );
                      }
                      catch {}
              
                      File.Move( TempPath, filePath );
              
                      TempPath = null;
                  }
              }
              

              【讨论】:

                【解决方案8】:

                这段代码 sn-p 展示了一种获得对文件的独占访问权的技术(在本例中为读取):

                // Try to open a file exclusively
                FileInfo fi = new FileInfo(fullFilePath);
                
                int attempts = maxAttempts;
                do
                {
                    try
                    {
                        // Try to open for reading with exclusive access...
                        fs = fi.Open(FileMode.Open, FileAccess.Read, FileShare.None);
                    }
                    // Ignore any errors... 
                    catch { }
                
                    if (fs != null)
                    {
                        break;
                    }
                    else
                    {
                        Thread.Sleep(100);
                    }
                }
                while (--attempts > 0);
                
                // Did we manage to open file exclusively?
                if (fs != null)
                {
                    // use open file....
                
                }
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-06-19
                  • 1970-01-01
                  • 2019-10-18
                  • 1970-01-01
                  • 1970-01-01
                  • 2015-08-11
                  相关资源
                  最近更新 更多