c# - 如何让 Win32_Process 查询更快?
问题描述
我有以下函数将进程信息提取到 DataTable 中:
public DataTable getProcesses()
{
DataTable dt = new DataTable();
dt.Columns.Add("ID");
dt.Columns.Add("Name");
dt.Columns.Add("Path");
dt.Columns.Add("User");
dt.Columns.Add("Priority");
string pid = "-";
string name = "-";
string path = "-";
string priort = "-";
string user = "-";
string query = "Select * From Win32_Process";
ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
ManagementObjectCollection processList = searcher.Get();
foreach (ManagementObject proc in processList)
{
pid = proc["ProcessID"].ToString();
name = proc["Name"].ToString();
if (proc["ExecutablePath"] != null)
{
path = proc["ExecutablePath"].ToString();
priort = proc["Priority"].ToString();
}
string[] argList = new string[2];
int returnVal = Convert.ToInt32(proc.InvokeMethod("GetOwner", argList));
if (returnVal == 0)
{
// return DOMAIN\user
user = argList[1] + "\\" + argList[0];
}
dt.Rows.Add(pid, name, path, user, priort);
}
return dt;
}
这最终会奏效并给我想要的回报,但执行需要 20-30 秒。
我想知道是否有任何方法可以优化功能,或者特别是查询,这很可能是“延迟”的来源。
编辑
在做了评论中建议的一些事情之后,平均时间已经下降到 15-20 秒,但这仍然太长了。最多4-5秒是可以忍受的。我仍然没有更改查询,有什么办法可以让它更快吗?
编辑 2
在应用了@NicoRiff 建议的一些更改后,仍然获得了相同的运行时,我进行了一些调试以查看实际花费了这么长时间。原来它是特定的一行:int returnVal = Convert.ToInt32(proc.InvokeMethod("GetOwner", argList));
,这将使我成为“拥有”每个进程的用户。
这条线大约需要 60 毫秒,而所有其他线需要 1-2 毫秒。超过 200 次迭代(这是我拥有的进程数,我只能想象使用更大的列表会花费的时间),总共需要大约 12-13 秒(仅针对那一行),给出 15-20总和。
既然我已经“挑出”了问题,我该如何优化该功能?
解决方案
从技术上讲,这并不能回答您的问题,因为它不使用 Win32_Process 查询。但是,使用 Powershell,它确实会在很短的时间内(~1.5 秒对~25 秒)产生相同的结果。
您需要在 X64 模式下运行它以询问 64 位进程,并且需要提升权限才能让 Powershell 返回用户名。
请注意,这是我第一次从 C# 调用 Powershell 脚本,可能有更好的方法来做。它还需要一些错误捕获以使其更健壮(不适合按原样生产)。
using System.Management.Automation;
using System.Management.Automation.Runspaces;
private DataTable getProcesses()
{
// Create the datatable and columns
DataTable dt = new DataTable();
dt.Columns.Add("ID");
dt.Columns.Add("Name");
dt.Columns.Add("Path");
dt.Columns.Add("User");
dt.Columns.Add("Priority");
dt.Columns.Add("BasePriority");
string script = $"get-process -IncludeUserName | select id, processname, path, username, priorityclass";
List<string[]> psOutput = new List<string[]>();
// Invoke Powershell cmdlet and get output
using (PowerShell ps = PowerShell.Create())
{
ps.AddScript(script);
var output = ps.Invoke();
if(ps.Streams.Error.Count > 0)
{
throw new Exception($"Error running script:{Environment.NewLine}{string.Join(Environment.NewLine, ps.Streams.Error.Select(e => e.ToString()))}");
}
// clean and split the output
psOutput.AddRange(output.Select(i => i.ToString().Replace("; ", ";").TrimStart("@{".ToCharArray()).TrimEnd('}').Split(';')));
}
// populate the DataTable
psOutput
.AsParallel() // this does not really help when not calling Process.GetProcessById
.Select(p => p.Select(f => f.Split("=")).ToDictionary(k => k.FirstOrDefault(), v => v.LastOrDefault()))
.Select(kv => new object[] { // "flatten" the dictionaries into object[]'s that will become the datarows
kv["Id"]
, kv["ProcessName"]
, kv["Path"]
, kv["UserName"]
, kv["PriorityClass"]
, Process.GetProcessById(int.Parse(kv["Id"])).BasePriority // if you need the numerical base priority - takes quite a bit longer though (Not sure how to get this via Powershell)
}
)
.ToList()
.ForEach(r => dt.Rows.Add(r)); // add each object[] to the datatable
// return the datatable
return dt;
}