You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在多个dotnet进程运行时终止特定.NET应用进程?

问题描述

我有一个主.NET应用,需要调用另一个次.NET应用,通过Process.Start()启动次应用的代码如下:

public void LaunchApp(string appFolder, string appName)
{
    ProcessStartInfo startInfo = new()
    {
        WorkingDirectory = appFolder,
        CreateNoWindow   = false,
        UseShellExecute  = true,
        FileName         = "dotnet",
        WindowStyle      = ProcessWindowStyle.Normal,
        Arguments        = $"{appName}.dll arg_1 arg_2",
    };

    try
    {
        using Process? process = Process.Start(startInfo);
        process?.WaitForExit();
    }
    catch
    {
        throw;
    }
}

但次应用不允许存在多个实例,启动前需要先终止已运行的实例。问题在于Process.GetProcesses()返回的进程名为dotnet,而非目标的secondary.dll,无法直接区分要终止的进程,若杀死所有dotnet进程会导致主应用也被终止。希望不借助终端命令解决该问题。

解决方案

1. 启动时记录次应用进程ID

启动次应用后,将其进程ID(PID)持久化存储(比如写入次应用目录下的PID文件),下次启动前读取该文件,检查对应PID的进程是否存活,且验证是否为目标次应用进程,再执行终止操作。

修改后的启动代码示例:

public void LaunchApp(string appFolder, string appName)
{
    string pidFilePath = Path.Combine(appFolder, $"{appName}.pid");
    // 先检查并终止已有实例
    if (File.Exists(pidFilePath))
    {
        if (int.TryParse(File.ReadAllText(pidFilePath), out int existingPid))
        {
            try
            {
                Process existingProcess = Process.GetProcessById(existingPid);
                // 额外验证:检查进程的命令行是否包含目标dll,防止PID复用
                if (IsTargetDotnetProcess(existingProcess, $"{appName}.dll"))
                {
                    existingProcess.CloseMainWindow();
                    // 等待进程退出,超时则强制杀死
                    if (!existingProcess.WaitForExit(3000))
                    {
                        existingProcess.Kill();
                    }
                }
            }
            catch (ArgumentException)
            {
                // 进程已不存在,删除无效PID文件
                File.Delete(pidFilePath);
            }
        }
    }

    // 启动新实例
    ProcessStartInfo startInfo = new()
    {
        WorkingDirectory = appFolder,
        CreateNoWindow   = false,
        UseShellExecute  = false, // 改为false才能获取进程ID
        FileName         = "dotnet",
        WindowStyle      = ProcessWindowStyle.Normal,
        Arguments        = $"{appName}.dll arg_1 arg_2",
    };

    try
    {
        using Process? process = Process.Start(startInfo);
        if (process != null)
        {
            // 写入PID文件
            File.WriteAllText(pidFilePath, process.Id.ToString());
            process.WaitForExit();
            // 进程退出后删除PID文件
            if (File.Exists(pidFilePath))
            {
                File.Delete(pidFilePath);
            }
        }
    }
    catch
    {
        throw;
    }
}

// 辅助方法:验证dotnet进程是否为目标应用
private bool IsTargetDotnetProcess(Process process, string targetDll)
{
    try
    {
#if WINDOWS
        using var searcher = new System.Management.ManagementObjectSearcher($"SELECT CommandLine FROM Win32_Process WHERE ProcessId = {process.Id}");
        foreach (var obj in searcher.Get())
        {
            string cmdLine = obj["CommandLine"]?.ToString() ?? string.Empty;
            return cmdLine.Contains(targetDll);
        }
#else
        // Linux/macOS 读取/proc/{pid}/cmdline
        string cmdLine = File.ReadAllText($"/proc/{process.Id}/cmdline").Replace('\0', ' ');
        return cmdLine.Contains(targetDll);
#endif
        return false;
    }
    catch
    {
        return false;
    }
}

注意:需将UseShellExecute改为false,否则无法获取启动后的进程ID;同时增加PID复用验证,避免误杀其他进程。

2. 通过进程命令行参数筛选目标进程

遍历所有dotnet进程,检查它们的命令行参数是否包含目标次应用的DLL名称,以此精准定位要终止的进程。

实现示例:

public void TerminateExistingInstance(string targetDll)
{
    foreach (Process process in Process.GetProcessesByName("dotnet"))
    {
        try
        {
            if (IsTargetDotnetProcess(process, targetDll))
            {
                process.CloseMainWindow();
                if (!process.WaitForExit(3000))
                {
                    process.Kill();
                }
            }
        }
        catch (Exception)
        {
            // 进程可能已退出,跳过
            continue;
        }
    }
}

// 复用前面的IsTargetDotnetProcess辅助方法
private bool IsTargetDotnetProcess(Process process, string targetDll)
{
    try
    {
#if WINDOWS
        using var searcher = new System.Management.ManagementObjectSearcher($"SELECT CommandLine FROM Win32_Process WHERE ProcessId = {process.Id}");
        foreach (var obj in searcher.Get())
        {
            string cmdLine = obj["CommandLine"]?.ToString() ?? string.Empty;
            return cmdLine.Contains(targetDll);
        }
#else
        string cmdLine = File.ReadAllText($"/proc/{process.Id}/cmdline").Replace('\0', ' ');
        return cmdLine.Contains(targetDll);
#endif
        return false;
    }
    catch
    {
        return false;
    }
}

使用时,在LaunchApp开头调用TerminateExistingInstance($"{appName}.dll")即可。

3. 让次应用自身实现单实例逻辑

在次应用内部通过互斥锁(Mutex)保证单实例,主应用无需处理终止逻辑,直接启动即可——次应用会自动检测是否已有实例,若存在则激活或退出。

次应用的Program.cs示例:

using System.Threading;

namespace SecondaryApp
{
    class Program
    {
        static void Main(string[] args)
        {
            // 全局唯一的互斥锁名称,避免与其他应用冲突
            using Mutex mutex = new Mutex(true, "Global\\SecondaryAppUniqueMutexName", out bool createdNew);
            if (!createdNew)
            {
                // 已有实例运行,可选择激活已有窗口或直接退出
                Console.WriteLine("应用已在运行中");
                return;
            }

            // 正常执行应用逻辑
            Console.WriteLine("次应用启动成功");
            // 模拟业务逻辑
            Console.ReadLine();
        }
    }
}

这种方式最可靠,无需主应用处理进程管理,次应用自身保证单实例。

内容的提问来源于stack exchange,提问作者Usman Mehmood

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 16:24:54