如何重新启动我的C# WinForm应用程序?

101

3
我对需要重新启动你的应用程序感到好奇,之前从未考虑过这种需求。你的情况是什么? - JeffH
1
我们的特殊情况是一个媒体播放器应用程序,它应该循环播放一些图像和Flash内容。应该连续运行数天而无需重新启动计算机,并且没有键盘/鼠标,因此没有用户交互。如果程序崩溃(未处理的异常),需要重新启动程序,而不是退出或显示错误。请参见http://stackoverflow.com/questions/773768/activex-flash-component-in-c-net-2-0-application-causes-memory-leak,了解为什么程序会出现我无法防止的异常。 :( - Adam Nofsinger
2
最终,我们应用程序的一个更好的解决方案是开发一个小的看门狗应用程序,该应用程序从主应用程序启动(如果尚未运行)。看门狗每隔大约10秒钟检查一次主应用程序是否仍在运行,如果没有,则启动一个进程。这种方法简单、优雅,并且比尝试从主应用程序重新启动更加稳定。 - Adam Nofsinger
4
重新启动的另一个原因是更换语言或下载更新。 - Andrew Truckle
1
但是如果...看门狗应用程序崩溃了呢? - Metoniem
显示剩余3条评论
25个回答

107

我使用了一种更简单的方法,它对我很有效:

Application.Restart();
Environment.Exit(0);

这样可以保存命令行参数,并且可以避免通常会阻止应用程序关闭的事件处理程序的影响。

Restart()调用尝试退出,无论如何开始新的实例并返回。Exit()调用然后终止进程,不会给任何事件处理程序运行的机会。在两个进程同时运行的非常短暂的时间段内可能存在问题,但在我的情况下没有问题,但在其他情况下可能存在问题。

Environment.Exit(0);中的退出代码0指定了干净的关闭。您也可以使用1退出以指定发生了错误。


1
简单而可靠。这应该是被接受的答案。我试图实现一个注销功能,它会清空所有全局变量,并显示应用程序刚开始时的状态。我本来打算只显示登录面板并将保存重要信息的所有全局变量重置为空。这让生活变得更加简单。谢谢! - DavidG
System.Windows.Forms.Application.Restart() 也适用于 WPF 应用程序。在 Windows 10 操作系统上进行了测试。 - NthDeveloper
什么?!你在开玩笑吧……C# <3。请注意,默认情况下不会触发OnClose()表单事件等。 - Artfaith

58

如果您在主应用程序表单中,请尝试使用以下内容

System.Diagnostics.Process.Start( Application.ExecutablePath); // to start new instance of application
this.Close(); //to turn off current app

7
类似但不同。Application.Exit对我不起作用,而this.Close()解决了问题。 - Darqer
1
也许 Enviorment.Exit(0) 也能完成任务。 - Dwza
2
"Enviorment.Exit"是一个不太合适的退出方式,因为它会阻止应用程序清理代码的运行,具有相当大的侵入性。大多数情况下都不是正确的选择。 - usr

42

很遗憾,您无法使用Process.Start()启动当前正在运行的进程的实例。根据Process.Start()文档: “如果进程已经在运行,则不会启动任何其他进程资源…”

这种技术在VS调试器下可以正常工作(因为VS会进行某种魔法使Process.Start认为进程尚未运行),但在非调试状态下将失败。(请注意,这可能是特定于操作系统的——我记得在我的一些测试中,它在XP或Vista上工作,但我可能只是记得在调试器下运行它。)

这种技术正是我目前正在工作的项目上最后一个程序员使用的技术,我一直在努力寻找解决方法。到目前为止,我只找到了一种解决方案,但它让我感觉很肮脏和笨拙:启动第二个应用程序,在后台等待第一个应用程序终止,然后重新启动第一个应用程序。我相信它会起作用,但是,呸。

编辑:使用第二个应用程序可行。我在第二个应用程序中所做的只是:

    static void RestartApp(int pid, string applicationName )
    {
        // Wait for the process to terminate
        Process process = null;
        try
        {
            process = Process.GetProcessById(pid);
            process.WaitForExit(1000);
        }
        catch (ArgumentException ex)
        {
            // ArgumentException to indicate that the 
            // process doesn't exist?   LAME!!
        }
        Process.Start(applicationName, "");
    }

(这只是一个非常简化的例子。实际代码有很多健全性检查、错误处理等)

我同意HiredMind的观点,实际上在回答后不久我也采用了相同的“看门狗程序”实现。抱歉,应该回来更新一下。我认为它不应该感觉太丑陋/恶心/肮脏。看门狗程序模式被广泛使用。 - Adam Nofsinger
你实际上不需要在磁盘上有第二个应用程序...你可以使用脚本并动态生成一个带有临时名称的应用程序...我认为这可能会减轻你因重新启动自己而感到内疚的感觉...或者你可以“发射”整个C#应用程序,编译它,保存到磁盘并执行它(肮脏的,肮脏的想法)... - Loudenvier
第一段不正确:“Process.Start”不查看正在运行的操作系统进程列表。此文档声明仅涉及“Process”类的对象实例。 “Process”类可以附加到正在运行的进程,但也可以处于未启动状态。在我看来,这是一个设计错误。最佳做法是永远不要重用“Process”实例,并在创建后立即启动它。理想情况下,使用静态“Process.Start”方法。然后,这个文档和设计缺陷就不会发挥作用。 - usr
这里展示的代码也不可靠,因为 WaitForExit(1000)。但是整个等待并不是启动新进程所必需的。这可能是您想要的附加行为,但启动新进程并不需要它。 - usr
我不知道现在情况如何,但九年前(咳咳),这绝对是正确的。 :-) - HiredMind

18

我可能有点晚参加这个活动,但这是我的简单解决方案,它在我使用的每个应用程序中都运行得很好:

        try
        {
            //run the program again and close this one
            Process.Start(Application.StartupPath + "\\blabla.exe"); 
            //or you can use Application.ExecutablePath

            //close this one
            Process.GetCurrentProcess().Kill();
        }
        catch
        { }

@Bitterblue 你发布的文档链接中提到:“如果该进程已经在运行,则不会启动其他进程资源。” 这是备注部分的第三行。我希望不是这样,但事实就是如此。 - HiredMind
@Tommix - 你可以获取当前可执行文件的名称。这不是什么大问题,你可以根据需要使其动态化。请阅读我在代码中添加的注释“或者你可以使用Application.ExecutablePath”。然而,这个解决方案简单明了。我发布它是因为我尝试了所有发布的解决方案,但没有一个达到了预期的效果。所以使用它还是不使用取决于你。 - Desolator
这对我很有效,即使我有一个互斥锁限制它只能有一个实例。 - Andrew Truckle
在尝试了以上所有方法后,这是在所有环境中最一致的解决方案。 - Nandostyle
还有一件事,如果您处于调试模式,这将启动另一个进程,剩余的运行程序将与正在进行的调试无关,实际上,Visual Studio 将认为应用程序刚刚关闭。它还会防止 Visual Studio 进行编译,因为 Bin 文件正在使用中。因此,请确保在重新启动后关闭打开的程序。 - Nandostyle
显示剩余5条评论

15

我曾经遇到过完全相同的问题,并且我也有一个要求防止重复实例的需求 - 我提出了一种替代解决方案,与HiredMind提出的解决方案(这个方案是可行的)不同。

我的做法是,将旧进程的processId(触发重新启动的进程)作为命令行参数启动新进程:

// Shut down the current app instance.
Application.Exit();

// Restart the app passing "/restart [processId]" as cmd line args
Process.Start(Application.ExecutablePath, "/restart" + Process.GetCurrentProcess().Id);

当新应用程序启动时,我首先解析cm行参数并检查是否存在带有进程ID的重启标识,然后等待该进程退出:

if (_isRestart)
{
   try
   {
      // get old process and wait UP TO 5 secs then give up!
      Process oldProcess = Process.GetProcessById(_restartProcessId);
      oldProcess.WaitForExit(5000);
   }
   catch (Exception ex)
   { 
      // the process did not exist - probably already closed!
      //TODO: --> LOG
   }
}

显然我并没有展示我所有的安全检查等内容。

即使不是最理想的情况 - 我认为这是一种有效的替代方案,这样您就不必再单独创建一个应用程序来处理重启。


1
在我看来,这个问题有一个更简洁的解决方案。我还有一个要求,即只允许单个实例并允许用户重新启动应用程序(例如当它崩溃时)。我差点实现了你的解决方案,但是我想到最好只需添加一个不同的命令行参数,允许多个实例运行。 - Dennis
嗯,你正在反转逻辑 - 我喜欢!唯一的问题是,如果有人找出了允许多个实例的命令行参数,可能会发生奇怪的事情:D - JohnIdol
很幸运的是,这是用户一直在要求的功能,所以这是双赢的局面。我更希望他们能够“找到”一个/allowMultipleInstances标志,而不是那个相当奇怪的/restart标志。 - Dennis
是的,如果您允许多个实例,那肯定是更好的解决方案 :) - JohnIdol
@JohnIdol 很不错。我想我会将我的标志改为“-waitForProcessToExit”或其他内容,但除此之外,这是一个更优雅的解决方案。目前我正在处理一个 ClickOnce 的问题,需要将一个 EXE 引用另一个 EXE,而这个解决方案可以解决这个问题。 - HiredMind

9
很简单,您只需要调用 Application.Restart() 方法,这将重新启动您的应用程序。您还必须使用错误代码退出本地环境:
Application.Restart();
Environment.Exit(int errorcode);
   

你可以创建一个错误代码枚举,以便应用程序能够高效退出。
另一种方法是直接退出应用程序,并使用可执行文件路径启动进程:
Application.Exit();
System.Diagnostics.Process.Start(Application.ExecutablePath);

对我来说效果很好,在我的情况下不需要看门狗应用程序。 - user685590

7

启动/退出方法

// Get the parameters/arguments passed to program if any
string arguments = string.Empty;
string[] args = Environment.GetCommandLineArgs();
for (int i = 1; i < args.Length; i++) // args[0] is always exe path/filename
    arguments += args[i] + " ";

// Restart current application, with same arguments/parameters
Application.Exit();
System.Diagnostics.Process.Start(Application.ExecutablePath, arguments);

这似乎比Application.Restart()更有效。

不确定如果您的程序防止多个实例,该如何处理。我的猜测是您最好启动第二个.exe文件,暂停一下,然后为您启动主应用程序。


2
如果应用程序受到多实例保护,则可能不起作用。 - majkinetor
是的,我有一种感觉,调用Application.Exit()只会导致某些消息被添加到需要被处理的队列中,因此在我的答案中这里的第二段代码可能确实不起作用。 - Adam Nofsinger
不幸的是,这种技术行不通(我希望它能行!它比我的解决方案简单得多)。它可以在Visual Studio调试器中工作,但在实践中却不行。请参阅我的答案,了解在调试器之外工作的解决方案。 - HiredMind
HiredMind可能是对的。最终我选择了一个看门狗模式的解决方案。 - Adam Nofsinger

4

试试这段代码:

bool appNotRestarted = true;

这段代码也必须放在函数中:
if (appNotRestarted == true) {
    appNotRestarted = false;
    Application.Restart();
    Application.ExitThread();
}

4

我想出了另一种解决方案,或许其他人也可以使用它。

string batchContent = "/c \"@ECHO OFF & timeout /t 6 > nul & start \"\" \"$[APPPATH]$\" & exit\"";
batchContent = batchContent.Replace("$[APPPATH]$", Application.ExecutablePath);
Process.Start("cmd", batchContent);
Application.Exit();

代码被简化了,因此需要注意异常等问题;)


3

我担心使用Process重新启动整个应用程序是错误的解决方法。

一个更简单的方法是修改Program.cs文件进行重启:

    static bool restart = true; // A variable that is accessible from program
    static int restartCount = 0; // Count the number of restarts
    static int maxRestarts = 3;  // Maximum restarts before quitting the program

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        while (restart && restartCount < maxRestarts)
        {
           restart = false; // if you like.. the program can set it to true again
           restartCount++;  // mark another restart,
                            // if you want to limit the number of restarts
                            // this is useful if your program is crashing on 
                            // startup and cannot close normally as it will avoid
                            // a potential infinite loop

           try {
              Application.Run(new YourMainForm());
           }
           catch {  // Application has crashed
              restart = true;
           }
        }
    }

2
值得一提的是,使用这个解决方案,您可以重新启动应用程序X次,然后退出应用程序以避免无限循环。 - RooiWillie
感谢@RooiWillie。我已经添加了一个计数器,以防程序不能正常退出。 - Andrew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接