使用C#编程以程序化方式终止进程树

63

我正在使用类似以下代码的方式通过程序启动Internet Explorer:

ProcessStartInfo startInfo = new ProcessStartInfo("iexplore.exe");
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.Arguments = "http://www.google.com";
Process ieProcess = Process.Start(startInfo);

这会在Windows任务管理器中生成2个进程。然后我尝试使用以下命令终止进程:

ieProcess.Kill();

这将导致任务管理器中的一个进程被关闭,而另一个进程保留。我尝试查找具有子进程的任何属性,但未找到。如何同时结束另一个进程?更一般地说,如何结束使用Process.Start启动的所有相关进程?


1
也许我漏掉了什么,但是为什么这会启动两个进程? - Etienne de Martel
@Etienne 我不知道,我也是这么想的。但是当我正常启动IE时,它也会启动2个进程。我不知道这是否值得注意,但我在64位机器上使用32位IE。 - robr
1
@Etienne de Martel:IE在其用户界面中使用多个进程,基本上是1个主进程+每个打开的选项卡一个子进程。 - Dirk Vollmar
很有趣,我以为它只是像Firefox一样工作,有一个进程和一堆不断挂起的线程。 - Etienne de Martel
感谢您修改过的代码片段/类。 - binball
11个回答

89

这对我非常有效:

/// <summary>
/// Kill a process, and all of its children, grandchildren, etc.
/// </summary>
/// <param name="pid">Process ID.</param>
private static void KillProcessAndChildren(int pid)
{
    // Cannot close 'system idle process'.
    if (pid == 0)
    {
        return;
    }
    ManagementObjectSearcher searcher = new ManagementObjectSearcher
            ("Select * From Win32_Process Where ParentProcessID=" + pid);
    ManagementObjectCollection moc = searcher.Get();
    foreach (ManagementObject mo in moc)
    {
        KillProcessAndChildren(Convert.ToInt32(mo["ProcessID"]));
    }
    try
    {
        Process proc = Process.GetProcessById(pid);
        proc.Kill();
    }
    catch (ArgumentException)
    {
        // Process already exited.
    }
}

2016年4月26日更新

Win7 x64上测试通过,使用的是Visual Studio 2015 Update 2。现在和3年前一样有效。

2017年11月14日更新

增加了对系统空闲进程的检查:if (pid == 0)

2018年3月2日更新

需要添加对System.Management命名空间的引用。请参考@MinimalTech的评论。如果您安装了ReSharper,它会自动为您完成此操作。

2018年10月10日更新

最常见的使用情况是终止自己C#进程启动的任何子进程。

在这种情况下,更好的解决方案是使用C#内的Win32调用使任何生成的进程成为一个子进程。这意味着当父进程退出时,Windows会自动关闭任何子进程,从而消除了上面代码的需求。如果您想让我发布代码,请告诉我。


孙子节点怎么办?我想这应该是一个递归方法。 - kerem
10
@kerem 嗯,我想是这样的。 - Dave Lawrence
5
你很棒!在.NET 4.0和VS2010环境下运作得非常好。只想提醒一下像我这样不知道的人,你必须使用System.Management命名空间。但是要能够使用它,你必须转到项目名称->右键单击->添加引用->.NET->并选择System.Management程序集,就像@Femaref在这里所指出的那样:https://dev59.com/EFDTa4cB1Zd3GeqPI2mJ - MinimalTech
应该是InvalidOperationException而不是ArgumentException,是吗? - rsmith54
2
请注意,Windows 会重用进程 ID。如果一个进程生成子进程并退出,留下其子进程运行,并且您的进程重新使用原始进程的 ID,则它看起来会有额外的子进程。上述方法将尝试杀死这些进程(可能有权限,也可能没有权限)。解决方法是添加一个检查进程启动时间的步骤,并确认它是在要杀死的树中的根进程之后启动的。 - EddPorter
显示剩余6条评论

34
如果有人需要一个dotnet core解决方案, Dotnet core 3.0
process.Kill(true);

请参见官方文档

Dotnet core 2.0

对于 .Net 2.0,dotnet cli 提供了一个基于 taskill 的实现,如上所述,并为基于 Unix 的系统提供了递归的 pgrep/kill。您可以在Github上找到完整的实现代码。不幸的是,该类是内部的,因此您需要将其复制到您的代码库中。

列出子进程(必须进行递归操作):

$"pgrep -P {parentId}"

进程中止:

$"kill -TERM {processId}"

在我看来,这应该是被接受的答案,因为.NET也可以在Linux上运行。 - André
1
这个针对Dotnet core 2.0的实现救了我的命哈哈。 - Caio César S. Leonardi

17

我不是任何提出的解决方案的粉丝。

这是我想出来的:

private static void EndProcessTree(string imageName)
{
    Process.Start(new ProcessStartInfo
    {
        FileName = "taskkill",
        Arguments = $"/im {imageName} /f /t",
        CreateNoWindow = true,
        UseShellExecute = false
    }).WaitForExit();
}

使用方法:

EndProcessTree("chrome.exe");

4
从编写自动编译程序的经验来看,在多个进程并行运行的生产环境中这将非常危险。举个简单的例子:尝试终止 explorer.exe 或 cmd.exe,会导致用户环境的很大一部分消失。 - Xenhat
这完全没用。Process.GetProcessByName() 返回了信息,但调用它没有失败,只是什么也没做。 - user1853517
你的代码被复制粘贴了。就像你一样,通过chrome.exe传递。不要认为taskkill是标准的Windows实用程序,在我的Windows 10机器上没有启动。 - user1853517

10

你应该调用 Process.CloseMainWindow() 方法,它会向进程的主窗口发送一条消息。可以把它想象成用户单击“X”关闭按钮或选择“文件”|“退出”菜单项。

与其终止所有Internet Explorer进程,更安全的方法是发送消息给Internet Explorer自己来关闭。那些进程可能在做任何事情,你需要让IE完成它的工作并在结束之前杀死它执行某个未来运行中可能很重要的操作是不明智的。这对于任何你要终止的程序都是适用的。


5
我认为这是处理具有用户界面的过程时首选的方式。 - robr
1
问题在于 Process.CloseMainWindow()Process.Close() 并不总是有效。 - Slogmeister Extraordinaire

9

如果有人感兴趣,我从其他页面中选了一个答案并稍作修改。它现在是一个自包含的类,具有静态方法。它没有适当的错误处理或日志记录。根据您自己的需求进行修改。将您的根进程提供给KillProcessTree即可。

class ProcessUtilities
{
    public static void KillProcessTree(Process root)
    {
        if (root != null)
        {
            var list = new List<Process>();
            GetProcessAndChildren(Process.GetProcesses(), root, list, 1);

            foreach (Process p in list)
            {
                try
                {
                    p.Kill();
                }
                catch (Exception ex)
                {
                    //Log error?
                }
            }
        }
    }

    private static int GetParentProcessId(Process p)
    {
        int parentId = 0;
        try
        {
            ManagementObject mo = new ManagementObject("win32_process.handle='" + p.Id + "'");
            mo.Get();
            parentId = Convert.ToInt32(mo["ParentProcessId"]);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
            parentId = 0;
        }
        return parentId;
    }

    private static void GetProcessAndChildren(Process[] plist, Process parent, List<Process> output, int indent)
    {
        foreach (Process p in plist)
        {
            if (GetParentProcessId(p) == parent.Id)
            {
                GetProcessAndChildren(plist, p, output, indent + 1);
            }
        }
        output.Add(parent);
    }
}

7

另一种解决方案是使用taskkill命令。我在我的应用程序中使用以下代码:

public static void Kill()
{
    try
    {
            ProcessStartInfo processStartInfo = new ProcessStartInfo("taskkill", "/F /T /IM your_parent_process_to_kill.exe")
            {
                WindowStyle = ProcessWindowStyle.Hidden,
                CreateNoWindow = true,
                UseShellExecute = false,
                WorkingDirectory = System.AppDomain.CurrentDomain.BaseDirectory,
                RedirectStandardOutput = true,
                RedirectStandardError = true
            };
            Process.Start(processStartInfo);
    }
    catch { }
}

4

你正在使用IE8或IE9吗?由于其新的多进程架构,这可能会启动多个进程。无论如何,请查看此其他答案以获取进程树并终止它。


是的,这回答了为什么会启动2个进程以及如何杀死进程树的问题。不知怎么的,我之前找不到这个答案,谢谢。 - robr
虽然我不得不承认,我本来以为它会简单很多。呕吐。 - robr
@robr: 如果代码能正常工作,那么成功只需要简单的复制粘贴即可。 - user203570

3
另一种非常有用的方法是使用Windows API for Job Objects。可以将进程分配给作业对象。这样进程的子进程会自动分配到相同的作业对象。
分配给作业对象的所有进程可以一次性终止,例如使用TerminateJobObject,它会:

终止与该作业相关联的所有进程。

此答案中的C#示例(基于此答案)使用JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE标志。
当与作业相关的最后一个句柄关闭时,导致所有与该作业相关的进程终止。

3

使用 .NET Core 3.0,可以使用一个方法完成此操作,它是已有的 Process.Kill() 方法的新重载。简而言之,在类型为 Process 的变量 process 上执行 process.Kill(true) 将终止该进程及其所有子进程。这是跨平台的自然行为。


这个在Linux上是否已经验证为正常工作?文档(https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process.kill?view=netstandard-2.1)提到了许多Windows特定的内容。 - 0b101010
根据之前的评论,我找到了这个链接(https://developers.redhat.com/blog/2019/10/29/the-net-process-class-on-linux/)。看起来在Linux上情况非常糟糕... - 0b101010
@0b101010 哪个部分明确说明在Linux中杀死进程树是非常受损的? - konrad.kruczynski
这是添加此功能的提交,显示其跨平台可用性:https://github.com/dotnet/runtime/commit/7bc0c52080b42f1196ee38f7e7501270fefb6dea(搜索KillTree)。 - konrad.kruczynski
我指的是那篇 RedHat 博客文章中记录的 Process 类的全部功能。 - 0b101010
@0b101010 通常情况下,Process类存在一些注意事项,但在Linux上(以及其他操作系统上),树状结构杀死功能表现良好。 - konrad.kruczynski

1
根据文档,Kill 方法是异步执行的。在调用 Kill 方法后,调用 WaitForExit 方法等待进程退出,或检查 HasExited 属性以确定进程是否已退出。
ProcessStartInfo startInfo = new ProcessStartInfo("iexplore.exe");
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.Arguments = "http://www.google.com";
Process ieProcess = Process.Start(startInfo);
ieProcess.Kill();
ieProcess.WaitForExit();

这是否实际上解决了多线程问题?我已经有9年没有处理过这个问题了,所以可能会忘记,但不确定这是否会修复? - robr
即使您通过调用Kill方法杀死了一个进程,如果您的程序在调用GetProcessById时没有等待该进程退出,它仍然可能会被检测到。 - Sameh

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