文章目录
- 前言
- 一、如何实现?
- 1、创建作业对象
- (1)、创建对象
- (2)、设置销毁作业时,关闭拥有的进程
- 2、子进程加入作业对象
- 3、销毁作业对象
- (1)、手动销毁
- (2)、所在进程结束自动销毁
- 二、完整代码
- 三、使用示例
- 1、正常退出自动结束子进程
- 2、异常退出自动结束子进程
- 总结
前言
多进程开发经常会遇到主进程关闭,子进程需要跟随主进程一同关闭。比如调ffmpeg命令行实现的录屏程序,录屏程序关闭,ffmpeg进程也需要退出。我们通常在程序关闭时调用Process.Kill()杀掉fmpeg进程即可。但是如果是强制或异常关闭录屏程序,ffmpeg将会变成僵尸进程残留在系统中。本文将提供一种解决此类问题的方法。
一、如何实现?
1、创建作业对象
(1)、创建对象
handle = CreateJobObject(IntPtr.Zero, null);
(2)、设置销毁作业时,关闭拥有的进程
var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION
{LimitFlags = 0x2000
};
var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{BasicLimitInformation = info
};int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))throw new Exception(string.Format("Unable to set information. Error: {0}", Marshal.GetLastWin32Error()));
2、子进程加入作业对象
AssignProcessToJobObject(handle, processHandle);
3、销毁作业对象
(1)、手动销毁
CloseHandle(handle);
(2)、所在进程结束自动销毁
二、完整代码
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace JobManagement
{#region Helper classes/// <summary>/// 作业对象,主要用于子进程管理。/// 目前版本是只支持作业销毁拥有的子进程退出/// 通常可以定义一个全局静态变量使用/// </summary>public class Job : IDisposable{[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]static extern IntPtr CreateJobObject(IntPtr a, string lpName);[DllImport("kernel32.dll")]static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, UInt32 cbJobObjectInfoLength);[DllImport("kernel32.dll", SetLastError = true)]static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);[DllImport("kernel32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]static extern bool CloseHandle(IntPtr hObject);private IntPtr handle;private bool disposed;public Job(){handle = CreateJobObject(IntPtr.Zero, null);var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION{LimitFlags = 0x2000};var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION{BasicLimitInformation = info};int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))throw new Exception(string.Format("Unable to set information. Error: {0}", Marshal.GetLastWin32Error()));}/// <summary>/// 进程加入到作业对象中/// </summary>/// <param name="processHandle">进程句柄</param>/// <returns></returns>public bool AddProcess(IntPtr processHandle){return AssignProcessToJobObject(handle, processHandle);}/// <summary>/// 进程加入到作业对象中/// </summary>/// <param name="processId">进程Id</param>/// <returns></returns>public bool AddProcess(int processId){return AddProcess(Process.GetProcessById(processId).Handle);}/// <summary>/// 销毁作业对象,手动调用则其拥有的所有进程都会退出/// </summary>public void Dispose(){Dispose(true);GC.SuppressFinalize(this);}/// <summary>/// 销毁作业对象,手动调用则其拥有的所有进程都会退出/// </summary>public void Close(){CloseHandle(handle);handle = IntPtr.Zero;}private void Dispose(bool disposing){if (disposed)return;if (disposing) { }Close();disposed = true;}}[StructLayout(LayoutKind.Sequential)]struct IO_COUNTERS{public UInt64 ReadOperationCount;public UInt64 WriteOperationCount;public UInt64 OtherOperationCount;public UInt64 ReadTransferCount;public UInt64 WriteTransferCount;public UInt64 OtherTransferCount;}[StructLayout(LayoutKind.Sequential)]struct JOBOBJECT_BASIC_LIMIT_INFORMATION{public Int64 PerProcessUserTimeLimit;public Int64 PerJobUserTimeLimit;public UInt32 LimitFlags;public UIntPtr MinimumWorkingSetSize;public UIntPtr MaximumWorkingSetSize;public UInt32 ActiveProcessLimit;public UIntPtr Affinity;public UInt32 PriorityClass;public UInt32 SchedulingClass;}[StructLayout(LayoutKind.Sequential)]public struct SECURITY_ATTRIBUTES{public UInt32 nLength;public IntPtr lpSecurityDescriptor;public Int32 bInheritHandle;}[StructLayout(LayoutKind.Sequential)]struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION{public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;public IO_COUNTERS IoInfo;public UIntPtr ProcessMemoryLimit;public UIntPtr JobMemoryLimit;public UIntPtr PeakProcessMemoryUsed;public UIntPtr PeakJobMemoryUsed;}public enum JobObjectInfoType{AssociateCompletionPortInformation = 7,BasicLimitInformation = 2,BasicUIRestrictions = 4,EndOfJobTimeInformation = 6,ExtendedLimitInformation = 9,SecurityLimitInformation = 5,GroupInformation = 11}#endregion
}
三、使用示例
1、正常退出自动结束子进程
.net8.0
using System.Diagnostics;
using JobManagement;
//创建作业对象
Job _job = new Job();
//打开记事本程序
var ps = new Process();
ps.StartInfo.FileName = "notepad.exe";
ps.Start();
//记事本程序进程加入到作业对象
_job.AddProcess(ps.Handle);
//等待3秒后退出程序,记事本程序会自动关闭
Thread.Sleep(3000);
效果预览
2、异常退出自动结束子进程
.net8.0
using System.Diagnostics;
using JobManagement;
//创建作业对象
Job _job = new Job();
//打开记事本程序
var ps = new Process();
ps.StartInfo.FileName = "notepad.exe";
ps.Start();
//记事本程序进程加入到作业对象
_job.AddProcess(ps.Handle);
while(true)Thread.Sleep(3000);
效果预览
总结
以上就是今天要讲的内容,本文讲述的内容是windows多进程开发中比较重要的技术,因为大部分场景主进程退出后子进程应该跟随退出,正常流程中通过代码可以在退出时关闭所有子进程,但是异常崩溃时则不行,会出现遗留子进程。而本文的方法就很好的解决的这个问题,而且也不需要编写任何关闭子进程的相关代码,方便且省心。