【问题标题】:Manipulating files block them操作文件会阻止它们
【发布时间】:2016-10-20 09:20:40
【问题描述】:

我正在编写一个使用 MEF 加载程序集的 WinForms 程序。这些程序集与可执行文件不在同一文件夹中。 由于我需要执行一些文件维护,因此在加载实际的 WinForm 之前,我在文件 Program.cs 中实现了一些代码,因此程序不会加载文件(即使是程序集)(或者不应该加载)。

我正在执行两个操作: - 将文件夹从一个位置移动到另一个位置 - 从存档中解压缩文件并覆盖移动文件夹中的 dll 文件(如果存档中的文件比移动的文件新)

问题是移动文件夹后,其中的文件被锁定,无法覆盖。我还尝试通过在移动完成后处理文件来逐个移动文件。

谁能解释一下为什么文件被阻止以及如何避免这种情况

谢谢

private static void InitializePluginsFolder()
    {
        if (!Directory.Exists(Paths.PluginsPath))
        {
            Directory.CreateDirectory(Paths.PluginsPath);
        }

        // Find archive that contains plugins to deploy
        var assembly = Assembly.GetExecutingAssembly();
        if (assembly.Location == null)
        {
            throw new NullReferenceException("Executing assembly is null!");
        }

        var currentDirectory = new FileInfo(assembly.Location).DirectoryName;
        if (currentDirectory == null)
        {
            throw new NullReferenceException("Current folder is null!");
        }

        // Check if previous installation contains a "Plugins" folder
        var currentPluginsPath = Path.Combine(currentDirectory, "Plugins");
        if (Directory.Exists(currentPluginsPath))
        {
            foreach (FileInfo fi in new DirectoryInfo(currentPluginsPath).GetFiles())
            {
                using (FileStream sourceStream = new FileStream(fi.FullName, FileMode.Open))
                {
                    using (FileStream destStream = new FileStream(Path.Combine(Paths.PluginsPath, fi.Name), FileMode.Create))
                    {
                        destStream.Lock(0, sourceStream.Length);
                        sourceStream.CopyTo(destStream);
                    }
                }
            }

            Directory.Delete(currentPluginsPath, true);
        }

        // Then updates plugins with latest version of plugins (zipped)
        var pluginsZipFilePath = Path.Combine(currentDirectory, "Plugins.zip");

        // Extract content of plugins archive to a temporary folder
        var tempPath = string.Format("{0}_Temp", Paths.PluginsPath);

        if (Directory.Exists(tempPath))
        {
            Directory.Delete(tempPath, true);
        }

        ZipFile.ExtractToDirectory(pluginsZipFilePath, tempPath);

        // Moves all plugins to appropriate folder if version is greater
        // to the version in place
        foreach (var fi in new DirectoryInfo(tempPath).GetFiles())
        {
            if (fi.Extension.ToLower() != ".dll")
            {
                continue;
            }

            var targetFile = Path.Combine(Paths.PluginsPath, fi.Name);
            if (File.Exists(targetFile))
            {
                if (fi.GetAssemblyVersion() > new FileInfo(targetFile).GetAssemblyVersion())
                {
                    // If version to deploy is newer than current version
                    // Delete current version and copy the new one

                    // FAILS HERE

                    File.Copy(fi.FullName, targetFile, true);
                }
            }
            else
            {
                File.Move(fi.FullName, targetFile);
            }
        }

        // Delete temporary folder
        Directory.Delete(tempPath, true);
    }

【问题讨论】:

  • 请分享您的代码。
  • 遗憾的是,我们甚至无法从中推测。
  • 对不起,我不想放很多代码,但我想它肯定会有所帮助。该方法是 Main 方法上调用的第一个方法
  • 你能确认你的程序正在生成锁吗?即运行一个程序来创建,另一个程序来提取/移动?您遇到了什么错误?
  • 与您的问题无关,但您不需要执行if (!Directory.Exists(...)) { Directory.CreateDirectory(...); } 组合。如果一个目录已经存在并且你调用 CreateDirectory 不会抛出错误。

标签: c# file mef filestream locked


【解决方案1】:

检查这部分代码中使用的GetAssemblyVersion()方法的实现:

if (File.Exists(targetFile))
{
  if (fi.GetAssemblyVersion() > new FileInfo(targetFile).GetAssemblyVersion())
  {
    // If version to deploy is newer than current version
    // Delete current version and copy the new one

    // FAILS HERE

    File.Copy(fi.FullName, targetFile, true);
  }
}

fi 变量的类型为FileInfoGetAssemblyVersion() 看起来像一个扩展方法。您应该检查如何从文件中检索程序集版本。如果此方法加载程序集,它还应该卸载它以释放文件。

单独的AppDomain 在您需要加载程序集、完成工作并在之后卸载它时很有帮助。下面是GetAssemblyVersion 方法的实现:

public static Version GetAssemblyVersion(this FileInfo fi)
{
  AppDomain checkFileDomain = AppDomain.CreateDomain("DomainToCheckFileVersion");
  Assembly assembly = checkFileDomain.Load(new AssemblyName {CodeBase = fi.FullName});
  Version fileVersion = assembly.GetName().Version;
  AppDomain.Unload(checkFileDomain);
  return fileVersion;
}

GetAssemblyVersion() 的以下实现可以在不将程序集加载到您的 AppDomain 的情况下检索程序集版本。 Thnx @usterdev 的提示。它还允许您在没有程序集引用的情况下获取版本:

public static Version GetAssemblyVersion(this FileInfo fi)
{
  return AssemblyName.GetAssemblyName(fi.FullName).Version;
}

【讨论】:

    【解决方案2】:

    您必须确保没有将Assembly 加载到您的域中以从中获取版本,否则文件将被锁定。

    通过使用AssemblyName.GetAssemblyName() 静态方法 (see MSDN),加载程序集文件,读取版本然后卸载 但不添加到您的域

    这里是FileInfo 的扩展:

    public static Version GetAssemblyVersion(this FileInfo fi)
    {
        AssemblyName an = AssemblyName.GetAssemblyName(fi.FullName);
        return an.Version;
    }
    

    【讨论】:

      【解决方案3】:

      以下语句锁定文件

      destStream.Lock(0, sourceStream.Length);
      

      但在那之后你还没有解锁文件。也许这就是你的问题的原因。

      【讨论】:

      • 谢谢,但不幸的是,这不是原因。复制后删除锁定或添加解锁不能解决问题。
      【解决方案4】:

      我会开始检查你的程序是否真的已经加载了程序集。

      两个建议:

      1 - 在调用 InitializePluginsFolder 之前调用这样的方法

      static void DumpLoadedAssemblies()
      {
          var ads = AppDomain.CurrentDomain.GetAssemblies();
          Console.WriteLine(ads.Length);
          foreach (var ad in ads)
          {
              Console.WriteLine(ad.FullName);
              // maybe this can be helpful as well
              foreach (var f in ad.GetFiles())
                  Console.WriteLine(f.Name);
              Console.WriteLine("*******");
          }
      }
      

      2 - 在 Main 的第一行,注册 AssemblyLoad 事件并将 Loaded Assembly 转储到事件处理程序中

      public static void Main()
      {
          AppDomain.CurrentDomain.AssemblyLoad += OnAssemlyLoad;
          ...
      }
      static void OnAssemlyLoad(object sender, AssemblyLoadEventArgs args) 
      {
          Console.WriteLine("Assembly Loaded: " + args.LoadedAssembly.FullName);
      }
      

      【讨论】:

        【解决方案5】:

        您肯定使用 AssemblyName.GetAssemblyName 加载程序集,不幸的是 .NET 没有在不加载程序集的情况下检查程序集元数据的常规方法。为避免这种情况,您可以:

        1. 按照 Nikita 的建议在单独的 AppDomain 中加载程序集,我可以添加:使用 ReflectionOnlyLoad 加载它
        2. 或者像 Reflector 一样使用 Mono.Cecil 库获取程序集版本
        3. 只是为了完整性:实际上您可以在两个阶段将程序集加载到同一个 AppDomain 而不锁定程序集文件:将文件内容读入 byte[] 并使用 Assembly.Load(byte[] rawAssembly) 但这种方式具有严重的“加载上下文”问题以及您将如何处理几个加载的程序集:)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-12-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-09-23
          • 1970-01-01
          • 2018-05-04
          • 1970-01-01
          相关资源
          最近更新 更多