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


86

开发C#.NET 2.0 WinForm应用程序。需要应用程序关闭并自行重启。

Application.Restart();

上述方法已被证明是不可靠的

有什么更好的重启应用程序的方法?


3
我很好奇是否需要重新启动您的应用程序。在您遇到什么情况之前,我从未想过这种需求。
JeffH

我们的特殊情况-媒体播放器应用程序应该循环运行一些图像和Flash内容。应连续运行数天而无需重新启动计算机,并且没有键盘/鼠标,因此没有用户交互。如果程序崩溃(未处理的异常),则需要重新启动程序,而不退出或显示错误。 stackoverflow.com/questions/773768/… 看到了为什么程序会不断出现我无法阻止的异常。:(
亚当·诺夫辛格

1
最后,对于我们的应用程序来说,更好的解决方案是开发一个小型的Watchdog应用程序,该应用程序可以从主应用程序启动(如果尚未运行)。看门狗仅每10秒钟左右检查一次,以查看主应用程序是否仍在运行一个进程,如果没有运行,则启动一个进程。与尝试从主应用程序重新启动相比,它更简单,更优雅且更坚固。
亚当·诺夫辛格

2
重新启动的另一个原因是您更改语言或下载更新。
安德鲁·特拉克

1
但是,如果...看门狗应用程序崩溃了怎么办?
Metoniem

Answers:


39

不幸的是,您不能使用Process.Start()启动当前正在运行的进程的实例。根据Process.Start()文档:“如果该进程已经在运行,则不会启动其他进程资源...”

该技术在VS调试器下可以很好地工作(因为VS做了某种魔术,导致Process.Start认为进程尚未运行),但是当不在调试器下运行时将失败。(请注意,这可能是特定于OS的-我似乎记得在某些测试中,它可以在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,实际上我在编写答案后不久就采用了相同的“看门狗程序”实现。抱歉,应该回到这里进行更新。我认为它应该不会丑陋/令人讨厌/肮脏。看门狗程序模式已被广泛使用。
亚当·诺夫辛格

您实际上并不需要磁盘上的第二个应用程序...您可以使用脚本并使用临时名称即时生成它...我认为这可以减轻您为重新启动而拥有第二个应用程序的内sense感您自己的...或者您可以“发射”整个C#应用程序,对其进行编译,将其保存到磁盘中并执行它(肮脏,肮脏的想法)
。– Loudenvier

第一段不正确:Process.Start没有查看正在运行的OS进程的列表。本文档语句只谈论那个对象实例的的Process类。的Process类可被连接到正在运行的过程,但它也可以是在未启动状态。我认为这是一个设计问题。IMO的最佳实践是永远不要重用 Process 实例,并在创建后立即启动它。理想情况下,使用静态 Process.Start方法。这样,该文档和设计缺陷就永远不会发挥作用。
usr

由于,此处显示的代码也不可靠WaitForExit(1000)。但是不必等待整个过程就可以开始新的过程。这可能是您想要的其他行为,但是不需要启动新流程。
usr

我现在不知道,但是九年前(哎呀),这绝对是正确的。:-)
HiredMind

72

一个对我有用的简单得多的方法是:

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

尽管事件处理程序通常会阻止应用程序关闭,但这样做可以保留命令行参数并可以正常工作。

Restart()调用尝试退出,无论如何启动新实例并返回。然后,Exit()调用将终止该过程,而不会给任何事件处理程序一个运行的机会。这两个进程都在很短的时间内运行,这对我来说不是问题,但在其他情况下也可能。

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


简单但可靠。这应该是公认的答案。我正在尝试实现一种注销功能,该功能将清空所有全局变量,并且看起来就像应用程序刚刚启动一样。我将只显示用于登录的面板,并将所有保留重要信息的全局变量从数据库重置为空。这使生活变得更加简单。谢谢!
DavidG

System.Windows.Forms.Application.Restart()也适用于WPF应用程序。已在Windows 10操作系统上测试。
NthDeveloper

哇?!你在开玩笑...对吗?C#<3。请记住,默认情况下,这不会引发OnClose()表单事件或类似事件。
F8ER

56

如果您使用的是主应用程序表单,请尝试使用

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
MayEnviorment.Exit(0)也可以做。
Dwza

2
Enviorment.Exit是一个肮脏且具有侵入性的出口,因为它阻止了应用程序清理代码的运行。大多数情况下,这不是正确的选择。
usr

16

我可能参加聚会很晚,但这是我的简单解决方案,它对我拥有的每个应用程序都具有吸引力:

        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
        { }

在Win-7-x64和Win-XP-x32上效果很好。我对文档中的任何约束一无所知。这应该是公认的答案。
Bitterblue 2014年

@Bitterblue指向您发布的文档的链接显示“如果该进程已在运行,则不会启动任何其他进程资源。” 这是备注部分的第三行。我希望不是这样,而是这样。
HiredMind 2015年

@HiredMind我想我们可以争论一下“约束”对我们每个人的意义。但是该部分的标题为“备注”而不是“约束”。所以我可以忍受。您为什么不希望这样呢?您有遇到任何问题吗?因为我没有。完美地为我工作。
Bitterblue 2015年

@HiredMind这不是什么私人的东西,只是使用第二个应用程序或脚本来重启我的C#应用​​程序的想法对我来说是令人反感的。到目前为止,Win7可以在发布模式下完美地执行此方法(当用户更改语言=不重要时,我使用它来重新启动我的应用程序)。因此,我宁愿告诉用户手动重新启动应用程序,也不愿使用我自己不赞成的方法。我过去使用过脚本,但我完全不喜欢它。
Bitterblue 2015年

@BitterBlue我同意使用第二个应用程序很烂-我在回答中说了这一点。但是您是否在调试器之外尝试了单应用程序方法?留下此答案后,它在Windows的某些版本上不起作用,并且Release或Debug模式与此无关。如果它对您有用,那就太好了!但是我被这个问题咬了,得到的是数百名愤怒的用户。
HiredMind 2015年

12

我有同样的问题,我也有防止重复实例的要求-我针对HiredMind提出的方案提出了另一种解决方案(可以很好地工作)。

我正在做的是使用cmd行参数将旧进程(触发重新启动的进程)的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行args并检查是否带有processId的重新启动标志在那里,然后等待该进程退出:

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
恕我直言,对此有一个更整洁的解决方案。我还要求具有单个实例,并允许用户重新启动应用程序(例如,当应用程序崩溃时)。我几乎实现了您的解决方案,但是我想到最好是简单地添加一个允许多个实例运行的不同命令行参数。
丹尼斯,

嗯,你在颠倒逻辑-我喜欢!唯一的问题是,如果有人找出允许多个实例的cmd line参数,可能会发生奇怪的事情:D
JohnIdol

好吧,我很幸运,这是用户一直在要求的功能,所以它是双赢的。我宁愿他们“找到”一个/allowMultipleInstances旗帜,而不是/restart一个奇怪的旗帜。
丹尼斯,

是的,如果您允许使用多个实例,那肯定是更好的解决方案:)
JohnIdol 2011年

@JohnIdol尼斯。我想我将标志更改为“ -waitForProcessToExit”或其他内容,但这是一个更优雅的解决方案。现在,我正在处理从另一个EXE引用一个EXE的ClickOnce问题,这将解决该问题。
HiredMind

6

开始/退出方法

// 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()只会导致将某些消息添加到需要泵送的队列中,因此答案中的第二部分代码确实可能无法正常工作。
亚当·诺夫辛格

不幸的是,这种技术不起作用(我希望它能起作用!它比我的解决方案简单得多)。它可以在Visual Studio调试器中运行,但实际上无法运行。请参阅我的答案,以获取在调试器之外运行的灵活解决方案。
HiredMind

HiredMind可能是正确的。我最终选择了看门狗模式解决方案。
亚当·诺夫辛格

6

它很简单,您只需要调用一些Application.Restart()方法即可调用您的应用程序以重新启动。但是,您必须使用错误代码退出本地环境:

Application.Restart();
Environment.exit(int errorcode);

您可以为其创建错误代码的枚举,以便可以有效地使用它。
另一种方法是仅退出应用程序并启动具有可执行路径的进程:

Application.exit();
System.Diagnostics.Process.Start(Application.ExecutablePath);

对我来说效果很好,就我而言不需要看门狗应用程序。
user685590

3

试试这个代码:

bool appNotRestarted = true;

此代码也必须在函数中:

if (appNotRestarted == true) {
    appNotRestarted = false;
    Application.Restart();
    Application.ExitThread();
}

3

我想出了另一种解决方案,也许任何人都可以使用它。

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

代码已简化,因此请注意异常和内容;)


2

您忘记了传递给当前正在运行的实例的命令行选项/参数。如果您不传递这些信息,那么您就不会进行真正的重启。Process.StartInfo使用流程参数的副本设置,然后开始。

例如,如果您的流程以开头myexe -f -nosplash myfile.txt,则仅在myexe没有所有这些标志和参数的情况下执行方法。


2

我希望新应用程序在旧应用程序关闭后启动。

使用process.WaitForExit()等待您自己的进程关闭是没有意义的。它总是会超时。

因此,我的方法是使用Application.Exit()然后等待一段时间,但允许处理事件一段时间。然后使用与旧的相同的参数启动一个新的应用程序。

static void restartApp() {
    string commandLineArgs = getCommandLineArgs();
    string exePath = Application.ExecutablePath;
    try {
        Application.Exit();
        wait_allowingEvents( 1000 );
    } catch( ArgumentException ex ) {
        throw;
    }
    Process.Start( exePath, commandLineArgs );
}

static string getCommandLineArgs() {
    Queue<string> args = new Queue<string>( Environment.GetCommandLineArgs() );
    args.Dequeue(); // args[0] is always exe path/filename
    return string.Join( " ", args.ToArray() );
}

static void wait_allowingEvents( int durationMS ) {
    DateTime start = DateTime.Now;
    do {
        Application.DoEvents();
    } while( start.Subtract( DateTime.Now ).TotalMilliseconds > durationMS );
}


2
public static void appReloader()
    {
        //Start a new instance of the current program
        Process.Start(Application.ExecutablePath);

        //close the current application process
        Process.GetCurrentProcess().Kill();
    }

Application.ExecutablePath返回您的aplication .exe文件路径,请按照调用的顺序进行操作。您可能希望将其放在try-catch子句中。


1

如何创建bat文件,在关闭之前运行批处理文件,然后关闭当前实例。

批处理文件执行以下操作:

  1. 等待循环检查该进程是否已退出。
  2. 开始过程。

这是一个穷人的看门狗应用程序,我在上面进行了详细介绍。真正的看门狗应用程序的主要好处是,即使原始应用程序死了,它也可以处理而不会剥离某些东西。
亚当·诺夫辛格

@AdamNofsinger我现在看到了。我错过了那条评论。您可能希望使用所使用的解决方案来编辑​​帖子。
AZ。

1

这是我的2美分:

即使对于不允许同时运行多个副本的应用程序,“启动新实例”->“关闭当前实例”序列也应起作用,因为在这种情况下,可能会向新实例传递一个命令行参数,该参数将指示正在进行重新启动因此,无需检查其他实例是否正在运行。如果绝对没有两个实例并行运行,那么等待第一个实例实际完成也可以实现。


1

我担心使用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;
           }
        }
    }

1
值得一提的是,使用此解决方案,您可以重新启动应用程序X次,然后退出应用程序以避免无限循环。
RooiWillie

谢谢@RooiWillie,我添加了一个计数器来执行该操作,以防程序无法正常退出。
安德鲁(Andrew)

0

我有一个类似的问题,但是我的问题与我不得不在必须运行24/7的应用程序中找不到的内存泄漏有关。我与客户达成一致,如果内存消耗超过了定义的值,则重新启动应用程序的安全时间为03:00 AM。

我尝试了Application.Restart,但是由于它似乎使用了某种机制来在新实例已经运行时启动新实例,因此我选择了另一种方案。我使用了文件系统处理的技巧,直到创建它们的进程终止。因此,从应用程序中,我将文件拖放到磁盘上,但没有Dispose()处理。我使用该文件发送“我自己”的可执行文件和启动目录(以增加灵活性)。

码:

_restartInProgress = true;
string dropFilename = Path.Combine(Application.StartupPath, "restart.dat");
StreamWriter sw = new StreamWriter(new FileStream(dropFilename, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite));
sw.WriteLine(Application.ExecutablePath);
sw.WriteLine(Application.StartupPath);
sw.Flush();
Process.Start(new ProcessStartInfo
{
    FileName = Path.Combine(Application.StartupPath, "VideoPhill.Restarter.exe"),
    WorkingDirectory = Application.StartupPath,
    Arguments = string.Format("\"{0}\"", dropFilename)
});
Close();

Close()最后将启动应用程序关闭,并且我在StreamWriter此处使用的文件句柄将保持打开状态,直到进程真正终止。然后...

Restarter.exe生效。它尝试以独占模式读取文件,阻止文件访问,直到主应用程序没死为止,然后启动主应用程序,删除文件并存在。我想这再简单不过了:

static void Main(string[] args)
{
    string filename = args[0];
    DateTime start = DateTime.Now;
    bool done = false;
    while ((DateTime.Now - start).TotalSeconds < 30 && !done)
    {
        try
        {
            StreamReader sr = new StreamReader(new FileStream(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite));
            string[] runData = new string[2];
            runData[0] = sr.ReadLine();
            runData[1] = sr.ReadLine();
            Thread.Sleep(1000);
            Process.Start(new ProcessStartInfo { FileName = runData[0], WorkingDirectory = runData[1] });
            sr.Dispose();
            File.Delete(filename);
            done = true;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        Thread.Sleep(1000);
    }
}

0

我使用以下内容,它确实满足您的需求:

ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
UpdateCheckInfo info = null;
info = ad.CheckForDetailedUpdate();
if (info.IsUpdateRequired)
{
    ad.UpdateAsync(); // I like the update dialog
    MessageBox.Show("Application was upgraded and will now restart.");
    Environment.Exit(0);
}

0

退出时需要终止Ram Cache中的所有应用程序,因此请先关闭该应用程序,然后重新运行

//点击注销按钮

foreach(Form frm in Application.OpenForms.Cast<Form>().ToList())
        {
            frm.Close();
        }
System.Diagnostics.Process.Start(Application.ExecutablePath);

0

使用Application.Restart()的问题是,它启动了一个新进程,但仍然保留了“旧”进程。因此,我决定使用以下代码段杀死旧进程:

            if(Condition){
            Application.Restart();
            Process.GetCurrentProcess().Kill();
            }

而且效果很好。在我的情况下,MATLAB和C#应用程序共享同一个SQLite数据库。如果MATLAB正在使用数据库,则Form-App应再次重新启动(+ Countdown),直到MATLAB重置数据库中的繁忙位为止。(仅供参考)


0

您可以将代码封装在一个函数中,当需要重新启动时,可以只调用该函数。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.