如何为正在运行的c#进程(AKA热部署)更新程序集?

时间:2010-02-25 16:42:20

标签: c# deployment

我有一个.exe程序集和一组引用的程序集,我希望能够为其部署更新的代码。我不需要实际更改正在运行的代码,但下次启动可执行文件时,我希望它能够获取更新的代码。目前,当我尝试复制正在运行的文件时,我收到有关其他进程正在使用的.exe的错误。

总结;如何在使用.exe时更新代码?

9 个答案:

答案 0 :(得分:18)

很容易做到。您可以重命名该文件,Windows在句柄上有锁,而不是文件的目录条目。现在您可以毫无问题地复制更新。剩下要做的就是在你的应用再次启动后摆脱重命名的文件。如有必要。

答案 1 :(得分:3)

我不认为这是可能的。例如,在部署没有停机时间的asp.net应用程序时,最佳做法是使用负载均衡器,这样您就可以删除一个实例,更新它,然后取下另一个实例进行更新。

答案 2 :(得分:2)

使用时无法更新装配体。此类情况的最佳选择是制作一个小型可执行文件,用于执行程序集的卷影副本,并从新位置启动它们。

这样,当用户启动程序时,您可以从部署站点进行卷影复制(本地),这可以随时被覆盖。

答案 3 :(得分:2)

ClickOnce为您提供了一些选择。应用程序启动后有一个update strategy“更新。”

对于更复杂的方案,有System.Deployment namespace。例如,您可以定期轮询应用程序中的更新。

答案 4 :(得分:2)

此类将重命名当前运行的可执行文件,如果它完成无异常,您只需编写新的可执行文件,然后重新启动,例如:

Ourself.Rename();
// Download or copy new version
File.Copy(newVersion, Ourself.FileName());
// Launch new version
System.Diagnostics.Process.Start(Ourself.FileName());
// Close current version
Close(); // Exit();

够简单吗?

class Ourself
{
    public static string FileName() {
        Assembly _objParentAssembly;

        if (Assembly.GetEntryAssembly() == null)
            _objParentAssembly = Assembly.GetCallingAssembly();
        else
            _objParentAssembly = Assembly.GetEntryAssembly();

        if (_objParentAssembly.CodeBase.StartsWith("http://"))
            throw new IOException("Deployed from URL");

        if (File.Exists(_objParentAssembly.Location))
            return _objParentAssembly.Location;
        if (File.Exists(System.AppDomain.CurrentDomain.BaseDirectory + System.AppDomain.CurrentDomain.FriendlyName))
            return System.AppDomain.CurrentDomain.BaseDirectory + System.AppDomain.CurrentDomain.FriendlyName;
        if (File.Exists(Assembly.GetExecutingAssembly().Location))
            return Assembly.GetExecutingAssembly().Location;

        throw new IOException("Assembly not found");
    }

    public static bool Rename()
    {
        string currentName = FileName();
        string newName = FileName() + ".ori";
        if (File.Exists(newName))
        {
            File.Delete(newName);
        }
        File.Move(currentName, newName);
        return true;
    }
}

答案 5 :(得分:1)

只是一个想法:尝试MEF和[导入[“http://someRemoteResource”]]

答案 6 :(得分:1)

以下内容如何:

  1. 将exe部署到更新文件夹。
  2. 每当应用程序启动时,它都会 检查更新文件夹。
  3. 如果不为空,请执行副本 程序
  4. 然后替换复制程序 现有的exe与其中的一个 更新文件夹
  5. 删除更新文件夹中的任何内容
  6. 然后重新启动exe

答案 7 :(得分:1)

最好的想法是已经建议的其他答案之一......就像使用MEF和/或ClickOnce重新组合一样。但是,这些解决方案无法帮助您实现“此部署”。它们要求您对exe进行更改,或者创建一个引导带可执行文件,这只会帮助您进行下一次部署。

对于此部署,您可以尝试这样做(我以前从未这样做过,但理论上它可以工作):

  1. 将您的新dll复制到某个子文件夹
  2. RunOnce registry key添加命令行xcopy命令,将新dll从子文件夹复制到您希望它进入的最终exe文件夹。
  3. 重新启动。
  4. RunOnce键包含命令行命令,这些命令在重新启动时运行一次,然后从注册表中删除,以便它们不再运行。这就是InstallShield允许您在其他应用程序使用时覆盖某些dll的方式。

答案 8 :(得分:0)

如果您想替换之前更新过的assembly,则有可能。我有一个应用程序,用户可以下载新的HtCore.dll。下载后,我将其重命名为HtCore.dll_new。在应用程序启动时,将替换库,如下所示:

修改您的App.cs

的构造函数
public App()
{
    try
    {
        var htCore = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "HtCore.dll");
        if (File.Exists($"{htCore}_new"))
        {
            File.Copy(htCore, $"{htCore}_backup", true);
            File.Delete(htCore);
            File.Move($"{htCore}_new", htCore);
            File.Delete($"{htCore}_new");
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}