在.NET Core应用程序中可靠地终止通过cmd进程启动的node.js进程

2024-01-15

我想解决什么问题

为了改进我的构建管道,我想添加端到端测试步骤。我计划通过 CLI 工具(.NET“控制台应用程序”)来实现它。该工具将启动并编排一些npm/node命令(进程)。

更具体地说,将会有:

  1. 后端流程;
  2. 前端流程;
  3. 和一个测试过程。

当测试过程(3)完成后,CLI 工具应终止后端(1)和前端(2) 优雅地处理,并返回0退出代码如果every编排的进程已成功终止。

Trouble

In my 最小、完整且可验证的示例 https://stackoverflow.com/help/mcve下面我正在尝试启动一个进程serviceAlikeProcess和一个失败的过程(brokenWithErrorProcess)。当后一个失败时,我试图通过强行终止前一个Kill(process) method.

!!!因为它是在这里建议 https://stackoverflow.com/a/43952716/482868, the node/npm进程正在通过启动cmd过程。 IE。我首先旋转一个cmd处理,然后写node test.js to its stdin溪流。这node进程启动得很好,但是当cmd进程稍后终止,node进程继续运行并产生输出。

我想这是由于以下事实而发生的cmd and node进程没有以父子关系链接(因为如果我手动终止cmd从任务管理器处理,我观察到相同的行为)。

Question

如何可靠地终止这两个进程?

Idea:我正在考虑捕捉node过程'pid然后终止两者cmd and node我自己处理,但我还没有找到一种方法来捕获它pid...

Code

程序.cs

using System;
using System.Diagnostics;
using System.IO;

namespace RunE2E
{
    public class Program
    {
        static string currentDirectory = Directory.GetCurrentDirectory();

        public static int Main(string[] args)
        {
            var serviceAlikeProcess = StartProcessViaCmd("node", "test.js", "");


            var brokenWithErrorProcess = StartProcessViaCmd("npm", "THIS IS NOT A REAL COMMAND, THEREFORE EXPECTED TO FAIL", "");
            brokenWithErrorProcess.Exited += (_, __) => KillProcess(serviceAlikeProcess);


            serviceAlikeProcess.WaitForExit();
            return serviceAlikeProcess.ExitCode;
        }

        private static Process StartProcessViaCmd(string command, string arguments, string workingDirectory)
        {
            workingDirectory = NormalizeWorkingDirectory(workingDirectory);

            var process = new Process
            {
                EnableRaisingEvents = true,
                StartInfo = new ProcessStartInfo
                {
                    FileName = "cmd",
                    Arguments = arguments,
                    WorkingDirectory = workingDirectory,
                    UseShellExecute = false,
                    RedirectStandardInput = true,
                    RedirectStandardError = true,
                    RedirectStandardOutput = true,
                    CreateNoWindow = true,
                }
            };

            process.ErrorDataReceived += (_, e) => handle(command, arguments, workingDirectory, "ERROR", e.Data);
            process.OutputDataReceived += (_, e) => handle(command, arguments, workingDirectory, "OUTPUT", e.Data);

            try
            {
                Console.WriteLine($"[{workingDirectory}] {command} {arguments}");

                var _ = process.Start();

                process.BeginOutputReadLine();
                process.StandardInput.WriteLine($"{command} {arguments} & exit");
            }
            catch (Exception exc)
            {
                Console.WriteLine($"[{workingDirectory}] {command} {arguments} : {exc}");
                throw;
            }
            return process;
        }

        static string NormalizeWorkingDirectory(string workingDirectory)
        {
            if (string.IsNullOrWhiteSpace(workingDirectory))
                return currentDirectory;
            else if (Path.IsPathRooted(workingDirectory))
                return workingDirectory;
            else
                return Path.GetFullPath(Path.Combine(currentDirectory, workingDirectory));
        }

        static Action<string, string, string, string, string> handle =
            (string command, string arguments, string workingDirectory, string level, string message) =>
            Console.WriteLine($"[{workingDirectory}] {command} {arguments} {level}: {message}");

        static void KillProcess(Process process)
        {
            if (process != null && !process.HasExited)
                process.Kill();
        }
    }
}

test.js

setInterval(() => {
    console.info(new Date());
}, 1000);

截图

以编程方式启动任何进程之前

‘cmd’进程被杀死后

控制台输出


我真的really不喜欢最终回答我自己的问题,尤其是当答案是基于一种黑客式的方式来实现结果时。

但是,我知道这可能会节省其他人的时间。所以,这是我的解决方案:

Code

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;

namespace RunE2E
{
    public class Program
    {
        static string currentDirectory = Directory.GetCurrentDirectory();

        public static int Main(string[] args)
        {
            var serviceAlikeProcessResult = StartProcessViaCmd("node", "test.js", "");
            var serviceAlikeProcess = serviceAlikeProcessResult.MainProcess;

            var brokenWithErrorResult = StartProcessViaCmd("npm", "THIS IS NOT A REAL COMMAND, THEREFORE EXPECTED TO FAIL", "");
            var brokenWithErrorProcess = brokenWithErrorResult.MainProcess;
            brokenWithErrorProcess.Exited += (_, __) =>
            {
                KillProcesses("Front-End", serviceAlikeProcessResult.MainProcess, serviceAlikeProcessResult.CreatedProcesses);
                KillProcesses("E2E-Test", brokenWithErrorResult.MainProcess, brokenWithErrorResult.CreatedProcesses);
            };

            serviceAlikeProcess.WaitForExit();
            return serviceAlikeProcess.ExitCode;
        }

        private static CommandStartResult StartProcessViaCmd(string command, string arguments, string workingDirectory)
        {
            workingDirectory = NormalizeWorkingDirectory(workingDirectory);

            var process = new Process
            {
                EnableRaisingEvents = true,
                StartInfo = new ProcessStartInfo
                {
                    FileName = "cmd",
                    Arguments = arguments,
                    WorkingDirectory = workingDirectory,
                    UseShellExecute = false,
                    RedirectStandardInput = true,
                    RedirectStandardError = true,
                    RedirectStandardOutput = true,
                    CreateNoWindow = true,
                },
            };
            var createdProcesses = new List<Process>();

            process.ErrorDataReceived += (_, e) => handle(command, arguments, workingDirectory, "ERROR", e.Data);
            process.OutputDataReceived += (_, e) => handle(command, arguments, workingDirectory, "", e.Data);

            var commandId = $"[{workingDirectory}] {command} {arguments}";
            try
            {
                WriteLine(commandId);

                createdProcesses = StartProcessAndCapture(commandId, process);

                process.BeginOutputReadLine();
                process.StandardInput.WriteLine($"{command} {arguments} & exit");
            }
            catch (Exception exc)
            {
                WriteLine($"{commandId}: {exc}");
                throw;
            }

            return new CommandStartResult
            {
                MainProcess = process,
                CreatedProcesses = createdProcesses,
            };
        }

        static List<Process> StartProcessAndCapture(string commandId, Process processToStart)
        {
            var before = Process.GetProcesses().ToList();
            var beforePidSet = new HashSet<int>(before.Select(process => process.Id));

            var _ = processToStart.Start();

            Thread.Sleep(3000);

            var after = Process.GetProcesses().ToList();
            var newlyCreatedProcessIdList = new HashSet<int>(after.Select(process => process.Id));
            newlyCreatedProcessIdList.ExceptWith(beforePidSet);
            var createdProcesses = after.Where(process => newlyCreatedProcessIdList.Contains(process.Id)).ToList();

            foreach (var process in createdProcesses)
                WriteLine($"{commandId} ||| [{process.Id}] {process.ProcessName}", ConsoleColor.Blue);

            return createdProcesses;
        }

        static string NormalizeWorkingDirectory(string workingDirectory)
        {
            if (string.IsNullOrWhiteSpace(workingDirectory))
                return currentDirectory;
            else if (Path.IsPathRooted(workingDirectory))
                return workingDirectory;
            else
                return Path.GetFullPath(Path.Combine(currentDirectory, workingDirectory));
        }

        static Action<string, string, string, string, string> handle =
            (string command, string arguments, string workingDirectory, string level, string message) =>
            {
                var defaultColor = Console.ForegroundColor;
                Write($"[{workingDirectory}] ");
                Write($"{command} ", ConsoleColor.DarkGreen);
                Write($"{arguments}", ConsoleColor.Green);
                Write($"{level} ", level == "" ? defaultColor : ConsoleColor.Red);
                WriteLine($": {message}");
            };

        static void KillProcesses(string prefix, Process baseProcess, List<Process> processList)
        {
            processList = baseProcess == null ?
                processList :
                processList.Where(process => process.Id != baseProcess.Id).Append(baseProcess).ToList();

            foreach (var process in processList)
                KillProcess(prefix, process);
        }

        static void KillProcess(string prefix, Process process)
        {
            if (process != null && !process.HasExited)
                try
                {
                    WriteLine(prefix + " | Kill (" + process.ProcessName + ") [" + process.Id + "]");
                    process.Kill();
                }
                catch (Win32Exception win32exc)
                {
                    WriteLine(prefix + " | Kill (" + process.ProcessName + ") [" + process.Id + "]: " + win32exc.Message);
                }
        }

        static void WaitForExit(Process process)
        {
            while (process.HasExited == false) { }
        }

        static object console = new object();
        static void Write(string text, ConsoleColor? color = null)
        {
            lock (console)
            {
                var original = Console.ForegroundColor;
                Console.ForegroundColor = color.HasValue ? color.Value : original;
                Console.Write(text);
                Console.ForegroundColor = original;
            }
        }
        static void WriteLine(string text = null, ConsoleColor? color = null)
        {
            lock (console)
            {
                var original = Console.ForegroundColor;
                Console.ForegroundColor = color.HasValue ? color.Value : original;
                Console.WriteLine(text);
                Console.ForegroundColor = original;
            }
        }
    }

    class CommandStartResult
    {
        public Process MainProcess { get; set; }
        public List<Process> CreatedProcesses { get; set; }
    }
}

此外,在处理 .NET Core 进程时,人们可能希望使用以下方法。

        private static CommandStartResult StartDotnetProcess(string arguments, string workingDirectory)
        {
            var command = "dotnet";
            workingDirectory = NormalizeWorkingDirectory(workingDirectory);

            var process = PrepareProcess(command, arguments, workingDirectory);
            var createdProcesses = new List<Process>();
            var commandId = $"[{workingDirectory}] {command} {arguments}";
            try
            {
                WriteLine(commandId);

                createdProcesses = StartProcessAndCapture(commandId, process);

                process.BeginOutputReadLine();
            }
            catch (Exception exc)
            {
                WriteLine($"{commandId} : {exc}");
                throw;
            }

            return new CommandStartResult
            {
                MainProcess = process,
                CreatedProcesses = createdProcesses,
            };
        }

        private static Process PrepareProcess(
            string command,
            string arguments,
            string workingDirectory
        )
        {
            var process = new Process
            {
                EnableRaisingEvents = true,
                StartInfo = new ProcessStartInfo
                {
                    FileName = command,
                    Arguments = arguments,
                    WorkingDirectory = workingDirectory,
                    UseShellExecute = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                },
            };

            process.ErrorDataReceived += (_, e) => handle(command, arguments, workingDirectory, "ERROR", e.Data);
            process.OutputDataReceived += (_, e) => handle(command, arguments, workingDirectory, "", e.Data);

            process.StartInfo.Environment.Add("ASPNETCORE_ENVIRONMENT", "Development");

            return process;
        }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在.NET Core应用程序中可靠地终止通过cmd进程启动的node.js进程 的相关文章

随机推荐

  • 数据未写入数据库

    我正在 CheckboxProcess CheckedChanged 事件中向数据库写入一个位值 然而 实际上什么也没有写出来 我里面有一些代码填充了适配器 但我把它拿出来了 有人能看出哪里出了问题吗 protected void Page
  • 禁用表单仍然允许子控件接收输入

    最近几天我在使用 delphi 时感到很头疼 我想做的很简单 在某个点阻止接口并在其他点之后启用 但听起来很简单 我无法弄清楚为什么设计允许某些事情 所以澄清一下 1 创建一个项目 2 在表单中放置一个编辑和一个按钮 编辑的Tab顺序必须是
  • Chrome / Safari 未填充 Flex 父级的 100% 高度

    我想要一个具有特定高度的垂直菜单 每个子项都必须填充父项的高度 并且文本居中对齐 孩子的数量是随机的 所以我必须使用动态值 Div container包含随机数量的子项 item 总是必须填充父级的高度 为了实现这一点 我使用了 Flexb
  • 创建 mozilla 扩展以在其中显示弹出窗口和 iframe

    我正在尝试开发一个 mozilla 扩展 我只需要在弹出窗口中显示 iframe 但不知道该怎么做 我的要求是 在顶部导航工具栏添加扩展按钮 单击扩展按钮时在弹出窗口上显示 iframe 我没有找到任何与此类似的教程 请帮我 谢谢 哈里普拉
  • PHP使用正则表达式将大写文件扩展名转换为小写

    我正在尝试实现以下转换 IM22 htp JPG gt IM22 htp jpg 到目前为止 我已经尝试了以下方法 但似乎不起作用 string IM22 htp JPG pattern w i replacement 1 strtolow
  • 在 p:datatable primefaces 之外找不到标识符为 h:selectBooleanCheckbox 的组件

    我的应用程序中有这段代码 我想将 h selectBooleanCheckbox 值提交到服务器 h selectBooleanCheckbox 位于 p tabView 内部和 p dataTable 外部 我想提交 h selectBo
  • Maven:自定义 web-app 项目的 web.xml

    我有一个 Web 应用程序 Maven 项目 我想根据正在运行的配置文件自定义 web xml 文件 我正在使用 Maven War plugin 它允许我定义一个 资源 目录 可以在其中过滤文件 然而 仅靠过滤对我来说还不够 更详细地说
  • 静态类中静态方法中的变量

    请考虑这段代码 1 public static class MyClass 2 3 public static DateTime MyMethod DateTime dt 4 5 DateTime temp new DateTime 6 t
  • 使用字符串数组声明 java 枚举

    我试图根据从数据库检索的数据声明枚举类型 我有一个方法 它返回表中所有行的字符串数组 我想将其转换为枚举类型 有没有办法用数组构造枚举 这是我尝试过的 但从它在 Eclipse 中的外观来看 这似乎只是创建了一个具有该名称的方法 publi
  • CodeMirror - 样式 Tern 智能感知标签

    我正在使用 CodeMirror 的 Tern 插件 它向编辑器添加了智能感知功能 即弹出内联提示标签 我想改变提示标签的样式 该怎么做 Tern 将 CSS 类名附加到提示框 您可以利用它们来设置样式 例如 CodeMirror Tern
  • 编写脚本来检查远程主机服务是否运行[重复]

    这个问题在这里已经有答案了 这是脚本 但即使 Apache 正在运行其 show stop 输出也是错误的 我使用的是 Ubuntu 12 04 ssh qn root ip if ps aux grep h ttpd gt dev nul
  • 发送电子邮件时出现 VBScript 错误“80040211”

    我在发送电子邮件时遇到错误 错误 80040211 这是我的代码的示例 Dim objMessage Set objMessage CreateObject CDO Message objMessage Subject Super Serv
  • 向元组添加值

    我最初使用 Idictonary 在 MVC 模型中存储字符串值 如下所示 public IDictionary
  • JavaScript 数组排序中的奇怪行为[重复]

    这个问题在这里已经有答案了 我在尝试对 JavaScript 数组进行排序时遇到了奇怪的行为 var arr a b C d e f g h I k arr sort function a b console log a b if a le
  • 为什么 Funcs 不接受超过 16 个参数?

    由于 Javascript 是我最精通的语言 因此我熟悉使用函数作为一等对象 我原本以为C 缺少这个功能 但后来我听说了Func and Action and delegate 我认为这非常棒 例如 您可以声明一个Func连接两个字符串并在
  • 我这里如何使用锁和条件?

    这里我有一个任务 我必须使用锁和条件 在方法 sum 和 randomSwap 中是我必须完成的任务 所以列举了我必须做的事情 首先是没有我编辑的方法中的类和任务 public class LockedDataObject extends
  • Darwin 10.15 上的自修改代码导致“格式错误的 mach-o 图像”?

    我有一个可以生成自修改代码的程序 请参阅https tigress wtf selfModify html https tigress wtf selfModify html如果你有兴趣的话 它在 x86 Darwin 和 Linux 上运
  • Zend 框架和 Couch DB

    Zend Framework 是否具有本机 Couch DB 支持 我只找到了 Matthew Weier O Phinney 的 Zend Couch http framework zend com wiki display ZFPROP
  • 如何使用 Elixir 生成随机 url 安全字符串

    我需要能够生成随机 url 安全字符串 以便我可以在链接中使用这些字符串 例如发送到用户电子邮件的激活链接 那么如何生成它呢 有没有办法只用 Elixir 来做到这一点 或者我必须使用一些库 您可以做的是生成一个 Base64 编码的字符串
  • 在.NET Core应用程序中可靠地终止通过cmd进程启动的node.js进程

    我想解决什么问题 为了改进我的构建管道 我想添加端到端测试步骤 我计划通过 CLI 工具 NET 控制台应用程序 来实现它 该工具将启动并编排一些npm node命令 进程 更具体地说 将会有 后端流程 前端流程 和一个测试过程 当测试过程