C# 创建Worker,杀死指定程序的线程

目录

  • [一. 需求](#一. 需求)
  • [二. 初始化项目](#二. 初始化项目)
  • [三. 程序代码](#三. 程序代码)
    • [3.1 `appsettings.json` 配置文件](#3.1 appsettings.json 配置文件)
    • [3.2 `ProcessMonitorOptions.cs` 配置类](#3.2 ProcessMonitorOptions.cs 配置类)
    • [3.3 `Program.cs` 主程序](#3.3 Program.cs 主程序)
    • [3.4 `Worker.cs` 工作类](#3.4 Worker.cs 工作类)
    • [3.5 说明](#3.5 说明)
  • [四. 发布](#四. 发布)
    • [4.1 修改`.csproj`配置文件](#4.1 修改.csproj配置文件)
    • [4.2 发布](#4.2 发布)

一. 需求

  • 检测当前用户运行的线程,一旦检测到指定的程序,就强行杀死程序对应的线程。
  • 该程序需要一直运行,不使用脚本的方式,需要创建一个.exe文件运行在后台。
  • 该程序不需要UI界面
    • 创建console项目的话,会有控制台窗口显示
    • 创建worker项目的话,没有控制台窗口

二. 初始化项目

worker 模板创建的是一个长期运行的后台服务程序(类似 Windows 服务 / Linux daemon)

🔷核心特点:

  • 基于 .NET Generic Host
  • 默认支持:
    • 日志(Logging)
    • 依赖注入(DI)
    • 配置(appsettings.json)

🔷适合:

  • 后台监控
  • 定时任务
  • 消息队列消费者
  • 系统服务
csharp 复制代码
// 创建项目后, 移动到项目中
dotnet new worker -n MyMonitorService
cd MyMonitorService

// 安装依赖
dotnet add package Microsoft.Extensions.Hosting

🔷项目创建完之后,修改MyMonitorService.csproj文件

  • 默认情况下自动开启自动Using,先将其关闭,手动Using
  • <ImplicitUsings>enable</ImplicitUsings> 改为 <ImplicitUsings>disable</ImplicitUsings>

三. 程序代码

3.1 appsettings.json 配置文件

  • 配置要监测的程序的线程名字
  • 最长存活时间
  • 每次监测的间隔
json 复制代码
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "ProcessMonitor": {
    "ProcessNames": [ "Weixin", "Hidemaru" ],
    "MaxMinutes": 1,
    "CheckIntervalSeconds": 10
  }
}

3.2 ProcessMonitorOptions.cs 配置类

🔷用于存放从配置文读取到的信息的配置类

csharp 复制代码
using System.Diagnostics.CodeAnalysis;

namespace MyMonitorService;

// 为了防止Trim发布时的误删
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public class ProcessMonitorOptions
{
	public string[] ProcessNames { get; set; } = [];
	public int MaxMinutes { get; set; } = 1;
	public int CheckIntervalSeconds { get; set; } = 10;
}

3.3 Program.cs 主程序

csharp 复制代码
using System.Threading;
using MyMonitorService;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;

Mutex? mutex = null;

try
{
    mutex = new Mutex(true, "MyMonitorService", out bool createdNew);
    if (!createdNew)
    {
        return;
    }

	// 获取当前Host的Builder对象
	IHostBuilder builder = Host.CreateDefaultBuilder(args);

	// 为Builder对象添加服务
	builder.ConfigureServices((context, services) =>
	{
		// 添加服务所需的配置文件类
		services.Configure<ProcessMonitorOptions>(
			context.Configuration.GetSection("ProcessMonitor")
		);

		// 添加服务所需的Worker
		services.AddHostedService<Worker>();
	});

	IHost host = builder.Build();
	host.Run();
}
finally
{
    mutex?.ReleaseMutex();
    mutex?.Dispose();
}

3.4 Worker.cs 工作类

csharp 复制代码
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

// 更加简洁的定义namespace的方法
namespace MyMonitorService;

public class Worker(ILogger<Worker> logger, IOptionsMonitor<ProcessMonitorOptions> optionsMonitor) : BackgroundService
{
	// 日志对象
	private readonly ILogger<Worker> _logger = logger;
	// 配置文件对象
	private readonly IOptionsMonitor<ProcessMonitorOptions> _optionsMonitor = optionsMonitor;

	protected override async Task ExecuteAsync(CancellationToken stoppingToken)
	{
		while (!stoppingToken.IsCancellationRequested)
		{
			try
			{
				// 每次循环都获取最新的配置
				var options = _optionsMonitor.CurrentValue;

				// 读取配置文件中的线程名
				foreach (var processName in options.ProcessNames)
				{
					// 获取线程名对象
					var processes = Process.GetProcessesByName(processName);

					foreach (var proc in processes)
					{
						// 如果当前线程的运行时间 < 指定的最大时间的话
						var runTime = DateTime.Now - proc.StartTime;
						if (runTime.TotalMinutes < options.MaxMinutes)
						{
							// 跳过, 不杀死线程
							continue;
						}

						// 杀死指定的线程
						try
						{
							_logger.LogWarning("Killing {Process} PID={Pid}", processName, proc.Id);
							proc.Kill();
						}
						catch (Exception ex)
						{
							_logger.LogError(ex, "Kill error: {Process}", processName);
						}
					}
				}

				// 延时
				await Task.Delay(options.CheckIntervalSeconds * 1000, stoppingToken);
			}
			catch (TaskCanceledException)
			{
				return;
			}
			catch (Exception ex)
			{
				_logger.LogError(ex, "Main loop error");
			}
		}
	}
}

3.5 说明

🔷传统写法

csharp 复制代码
public class Worker : BackgroundService
{
	private readonly ILogger<Worker> _logger;
	private readonly IOptionsMonitor<ProcessMonitorOptions> _options;

	public Worker(
		ILogger<Worker> logger,
		IOptionsMonitor<ProcessMonitorOptions> options)
	{
		_logger = logger;
		_options = options.CurrentValue;
	}

	protected override async Task ExecuteAsync(CancellationToken stoppingToken)
	{
		。。。省略。。。
	}
}

🔷C#12的 主构造函数(Primary Constructor),类定义时直接声明构造函数参数。

  • services.AddHostedService()

    → 把 Worker 注册为 IHostedService

  • .NET 在启动 Host 时:

    → 从 DI 容器解析 IHostedService

  • DI 容器创建 Worker 实例时:

    → 分析构造函数参数(构造函数注入)

  • 发现需要:

    • ILogger<Worker>
    • IOptionsMonitor<ProcessMonitorOptions>
  • 容器去查:

    • ILogger<Worker> → 已由 CreateDefaultBuilder 注册
    • IOptionsMonitor<T> → 由 Configure<T>() 注册
  • 然后实例化:

    new Worker(logger, optionsMonitor)

csharp 复制代码
public class Worker(ILogger<Worker> logger, IOptionsMonitor<ProcessMonitorOptions> optionsMonitor) : BackgroundService
{
	// 日志对象
	private readonly ILogger<Worker> _logger = logger;
	// 配置文件对象
	private readonly IOptionsMonitor<ProcessMonitorOptions> _optionsMonitor = optionsMonitor;

	protected override async Task ExecuteAsync(CancellationToken stoppingToken)
	{
		。。。省略。。。
	}
}

四. 发布

4.1 修改.csproj配置文件

🔷配置要发布相关的设置

xml 复制代码
<Project Sdk="Microsoft.NET.Sdk.Worker">

  <PropertyGroup>
    <!-- 依赖的框架 -->
    <TargetFramework>net9.0</TargetFramework>
    <Nullable>enable</Nullable>
    <!-- 关闭自动Using, Using时需要手动导入 -->
    <ImplicitUsings>disable</ImplicitUsings>
    <!-- 自动生成的 -->
    <UserSecretsId>dotnet-MyMonitorService-e4e75285-dee7-4046-82ba-a35bbe1823bd</UserSecretsId>
    <!-- 不弹黑框控制台, 适合后台服务 -->
    <OutputType>WinExe</OutputType>
    <!-- 不输出调试信息 -->
    <DebugType>none</DebugType>

    <!-- 元数据 -->
    <ApplicationIcon>assets\app.ico</ApplicationIcon>
    <AssemblyTitle>My Monitor Service</AssemblyTitle>
    <Description>自动监控并关闭指定进程的后台服务</Description>
    <Company>XXX公司</Company>
    <Product>监视线程</Product>

    <!-- 版本 -->
    <AssemblyVersion>1.0.0.0</AssemblyVersion>
    <FileVersion>1.0.0.0</FileVersion>
    <InformationalVersion>1.0.0</InformationalVersion>
  </PropertyGroup>

  <ItemGroup>
    <!-- 依赖的包 -->
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.5" />
    <!-- 不发布开发环境配置 -->
    <Content Update="appsettings.Development.json">
      <CopyToOutputDirectory>Never</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </Content>	
  </ItemGroup>
  
</Project>

4.2 发布

  • -r win-x64:指定运行平台
  • --self-contained true:不依赖机器安装.NET
  • -p:PublishTrimmed=true:裁剪(Trim)未使用的依赖,有助于减少体积
  • -p:TrimMode=partial:部分裁减模式,可以防止裁减过度导致缺少依赖报错
  • -p:PublishSingleFile=true:单文件版本
csharp 复制代码
dotnet publish -c Release -r win-x64 --self-contained true -o ./publish
csharp 复制代码
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishTrimmed=true -p:TrimMode=partial -o ./publish
csharp 复制代码
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishTrimmed=true -p:TrimMode=partial -p:PublishSingleFile=true -o ./publish
csharp 复制代码
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o ./publish
相关推荐
Nuopiane6 小时前
C#基础(1)堆栈、GC与Marshal
unity·c#
FuckPatience6 小时前
Visual Studio C# 项目中文件后缀简介
开发语言·c#
游乐码13 小时前
c#泛型约束
开发语言·c#
hoiii18714 小时前
C# 基于 LumiSoft 实现 SIP 客户端方案
前端·c#
yongui4783416 小时前
C# 与三菱PLC通讯解决方案
开发语言·c#
jerryinwuhan17 小时前
RDD第二次练习
开发语言·c#
aini_lovee21 小时前
C# 快速搜索磁盘文件解决方案
开发语言·c#
派葛穆1 天前
汇川PLC-Unity3d与汇川easy521plc进行Modbustcp通讯
unity·c#
游乐码1 天前
C#List
开发语言·c#·list