【问题标题】:How to make Win32_Process query faster?如何让 Win32_Process 查询更快?
【发布时间】:2019-05-13 11:25:00
【问题描述】:

我有以下函数可以将进程信息提取到 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 秒。

我想知道是否有任何方法可以优化功能,或者特别是查询,这很可能是“延迟”的来源。

编辑

在完成 cmets 中建议的一些事情后,平均时间已降至 15-20 秒,但这仍然太长了。最多4-5秒是可以忍受的。我还没有更改查询,有什么办法可以让它更快吗?

编辑 2

在应用@NicoRiff 建议的一些更改后,仍然获得相同的运行时间,我进行了一些调试以查看实际花费了这么长时间。原来它是特定的一行:int returnVal = Convert.ToInt32(proc.InvokeMethod("GetOwner", argList));,这将使我成为“拥有”每个进程的用户。

这条线大约需要 60 毫秒,而其他所有线需要 1-2 毫秒。超过 200 次迭代(这是我拥有的进程数,我只能想象使用更大的列表会花费的时间),总共需要大约 12-13 秒(仅针对那一行),给出 15-20总计。

既然我已经“挑出”了问题,我该如何优化该功能?

【问题讨论】:

  • 30 秒的延迟几乎总是环境问题。有一类软件会在程序员的机器上看到一个突然出现的可执行文件,然后立即戳到下一个进程时,它会永远在程序员的机器上造成这样的问题。将其关闭并重试。
  • 我试过这个(我认为我关闭了正确的东西)并将时间减少到 15 秒,但这仍然很长。还有其他可能的原因吗?
  • 看看如果你使用.GetOwnerSid而不是.GetOwner,事情是否会变得更快,看看将SID映射到帐户是否是导致速度变慢的可能原因。还可以通过在 Powershell 中执行操作来检查基本查询本身是否有任何问题(gwmi win32_process 立即在我的机器上返回;由于out 参数,调用.GetOwner 涉及更多一点)。
  • .GetOwnerSid 并没有变得更快,也没有真正给我所需的输出。我检查了 powershell 上的查询,它立即运行,但显示整个输出大约需要 7-8 秒 - 不会冻结或任何东西。

标签: c# windows wmi


【解决方案1】:

从技术上讲,这并不能回答您的问题,因为它不使用 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;
}

【讨论】:

    【解决方案2】:

    我个人一直想摆脱使用DataTable。您可以使用List&lt;&gt; 或可以解决许多潜在问题的集合。

    话虽如此,您可能想查看ORMi 库以获取有关List&lt;&gt; 的WMI 信息。您可以通过以下方式实现您正在尝试的目标:

            WMIHelper helper = new WMIHelper("root\\CimV2");
    
            string pid = "-";
            string name = "-";
            string path = "-";
            string priort = "-";
            string user = "-";
    
            var processes = helper.Query("Select * From Win32_Process").ToList();
    
            foreach (var p in processes)
            {
                pid = p.ProcessID;
                name = p.Name;
                path = p.ExecutablePath ?? String.Empty;
                priort = p.Priority ?? String.Empty;
            }
    

    以上代码适用于dynamic 对象,不需要您编写任何模型。就是那个小代码。如果您需要使用方法,那么您可以声明您的模型并使用强类型对象:

    1) 定义您的模型:

    [WMIClass(Name = "Win32_Process", Namespace = "root\\CimV2")]
    public class Process
    {
        public int Handle { get; set; }
        public string Name { get; set; }
        public int ProcessID { get; set; }
        public string ExecutablePath { get; set; }
        public int Priority { get; set; }
    
        /// <summary>
        /// Date the process begins executing.
        /// </summary>
        public DateTime CreationDate { get; set; }
    
        public dynamic GetOwnerSid()
        {
            return WMIMethod.ExecuteMethod(this);
        }
    
        public ProcessOwner GetOwner()
        {
            return WMIMethod.ExecuteMethod<ProcessOwner>(this);
        }
    
        public int AttachDebugger()
        {
            return WMIMethod.ExecuteMethod<int>(this);
        }
    }
    
    public class ProcessOwner
    {
        public string Domain { get; set; }
        public int ReturnValue { get; set; }
        public string User { get; set; }
    }
    

    2) 查询 WMI

            List<Process> processes = helper.Query<Process>().ToList();
    
            foreach (Process p in processes)
            {
                pid = p.ProcessID;
                name = p.Name;
                path = p.ExecutablePath ?? String.Empty;
                priort = p.Priority ?? String.Empty;
    
                dynamic d = p.GetOwnerSid();
                ProcessOwner po = p.GetOwner();
            }
    

    即使对于这项任务,第二种方式可能看起来有点工作量太大,但您将获得更清晰和易于理解的代码。

    注意:我已经用 ORMi 尝试过你的代码,我在 1-2 秒内得到结果。正如其他人所说,这可能取决于您的环境。

    注意 2:始终仅在 SELECT 语句中使用您需要的属性。 WMI SELECT * 非常昂贵。始终指定属性。在您的情况下,它将是:

    Select ProcessID, Name, ExecutablePath, Priority From Win32_Process
    

    (ORMi 也会为您解决这个问题,因为它总是会查询模型上设置的属性。)

    【讨论】:

    • SELECT 的问题是,如果我不使用星号,GetOwner 方法将不起作用。
    • @S.M.它不起作用,因为您必须包含该类的 KEY。在 Win32_Process 的情况下,该属性是“Handle”。试试这个:从 Win32_Process 中选择 ProcessID、名称、ExecutablePath、优先级、句柄
    • 我刚刚对整个过程进行了一些调试,在每次迭代中花费比其他行更长的一行是int returnVal = Convert.ToInt32(proc.InvokeMethod("GetOwner", argList));,其他行大约需要 1-2ms。大约 200 次迭代,这是我拥有的进程数,它给了我 15 秒。事实证明,并不是查询会减慢它的速度。我可以对该功能做些什么?想不出任何办法让它“更短”。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-06-05
    • 1970-01-01
    • 1970-01-01
    • 2014-02-24
    • 1970-01-01
    • 2018-05-17
    • 2010-10-24
    相关资源
    最近更新 更多