可靠地杀死在.NET Core应用程序

时间:2018-04-18 20:18:51

标签: c# .net node.js cmd .net-core

我想要解决的是什么

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

更具体地说,将会:

  1. 后端流程;
  2. 前端流程;
  3. 和测试过程。
  4. 当测试过程(3)完成时,CLI工具应该优雅地终止后端(1)和前端(2)进程,并返回{{1如果每个协调过程已成功终止,则退出代码。

    故障

    在我的Minimal, Complete, and Verifiable example下方,我试图启动流程0和失败的流程(serviceAlikeProcess)。当后者失败时,我试图通过brokenWithErrorProcess方法强行终止前一个。

    !!! 由于suggested hereKill(process) / node进程正在通过npm进程启动。即我首先启动cmd进程,然后将cmd写入其node test.js流。 stdin进程启动得很好但是当node进程稍后终止时,cmd进程会继续运行并生成输出。

    我认为这是因为事实nodecmd进程未在父子关系中链接(因为如果我手动终止{{1}来自任务管理器的进程,我观察到相同的行为)。

    问题

    如何可靠地杀死这两个进程?

    想法:我在考虑抓住node流程' cmd,然后我自己终止了nodepid个进程,但我还没有办法捕获cmd ...

    代码

    Program.cs的

    node

    test.js

    pid

    截图

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

    enter image description here

    `cmd`进程被杀后

    enter image description here

    控制台输出

    enter image description here

1 个答案:

答案 0 :(得分:0)

我真的真的不喜欢最后回答我自己的问题,特别是当答案基于一种实现结果的黑客方式时。

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

代码

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