.NET获取进程所有者速度过慢,求更高效的Win32 API实现方案
性能瓶颈原因
- 每次访问
Process.Handle属性时,底层会调用OpenProcess申请高权限访问,系统会对每个进程做权限校验,大部分系统进程、保护进程会拒绝高权限访问,单次权限校验的开销叠加后就会拉高总耗时。 - 原有代码没有判断
OpenProcessToken的返回值,失败时直接触发异常捕获流程,异常本身的执行开销远高于正常的返回值判断,进一步拖慢速度。 - 每个进程都要单独走「打开进程→打开令牌→关闭令牌→关闭进程句柄」的流程,多次用户态/内核态切换的开销累加明显。
优化方案
方案1:最小改动优化现有代码
先替换进程句柄获取逻辑,用最低权限PROCESS_QUERY_LIMITED_INFORMATION打开进程,同时用返回值判断代替异常控制流程,不用改核心逻辑就能提升至少50%的速度:
using System.Security.Principal; using System.Runtime.InteropServices; using System.Diagnostics; public class Test { [DllImport("advapi32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CloseHandle(IntPtr hObject); private const uint TOKEN_QUERY = 0x0008; private const uint PROCESS_QUERY_LIMITED_INFORMATION = 0x1000; public static List<List<string>> GetProcessWithUsers() { var processes = Process.GetProcesses(); var result = new List<List<string>>(processes.Length); // 预分配容量减少GC开销 foreach (var proc in processes) { result.Add(new List<string> { proc.ProcessName, GetProcessUser((uint)proc.Id) }); proc.Dispose(); // 主动释放Process对象资源 } return result; } public static string GetProcessUser(uint processId) { IntPtr processHandle = IntPtr.Zero; IntPtr tokenHandle = IntPtr.Zero; try { processHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, processId); if (processHandle == IntPtr.Zero) return null; if (!OpenProcessToken(processHandle, TOKEN_QUERY, out tokenHandle)) return null; using WindowsIdentity wi = new WindowsIdentity(tokenHandle); return wi.Name; } catch { return null; } finally { if (tokenHandle != IntPtr.Zero) CloseHandle(tokenHandle); if (processHandle != IntPtr.Zero) CloseHandle(processHandle); } } }
方案2:用WTS API实现批量查询(性能最优)
如果需要极致性能,用WTSEnumerateProcesses API可以一次性拿到所有进程的所有者SID,不用逐个打开进程,280个进程的查询耗时通常在100ms以内:
using System.Runtime.InteropServices; using System.Security.Principal; using System.Collections.Generic; public class WtsProcessHelper { [DllImport("wtsapi32.dll", SetLastError = true)] private static extern bool WTSEnumerateProcesses(IntPtr hServer, int Reserved, int Version, out IntPtr ppProcessInfo, out int pCount); [DllImport("wtsapi32.dll")] private static extern void WTSFreeMemory(IntPtr pMemory); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct WTS_PROCESS_INFO { public uint SessionId; public uint ProcessId; public string pProcessName; public IntPtr pUserSid; } public static List<List<string>> GetAllProcessWithUser() { var result = new List<List<string>>(); IntPtr processInfoPtr = IntPtr.Zero; int processCount = 0; try { if (!WTSEnumerateProcesses(IntPtr.Zero, 0, 1, out processInfoPtr, out processCount)) return result; long currentPtr = processInfoPtr.ToInt64(); int structSize = Marshal.SizeOf(typeof(WTS_PROCESS_INFO)); for (int i = 0; i < processCount; i++) { WTS_PROCESS_INFO processInfo = Marshal.PtrToStructure<WTS_PROCESS_INFO>((IntPtr)currentPtr); string userName = null; if (processInfo.pUserSid != IntPtr.Zero) { try { SecurityIdentifier sid = new SecurityIdentifier(processInfo.pUserSid); userName = sid.Translate(typeof(NTAccount)).ToString(); } catch { } } result.Add(new List<string> { processInfo.pProcessName, userName }); currentPtr += structSize; } } finally { if (processInfoPtr != IntPtr.Zero) WTSFreeMemory(processInfoPtr); } return result; } }
内容的提问来源于stack exchange,提问作者Tomalak
相关产品推荐
相关产品推荐

