Delphi VirtualStringTree使用缓存系统更新数据

Delphi VirtualStringTree使用缓存系统更新数据,delphi,caching,virtualtreeview,tvirtualstringtree,Delphi,Caching,Virtualtreeview,Tvirtualstringtree,嗯,我正在使用VirtualStringTree创建一种流程管理器 我遇到了麻烦,因为更新树时计时器设置为1000ms(cpu使用率太高,无法让我的应用程序检索大量数据(填充大约20列) 所以我想知道如何构建一种缓存系统,这样只有当某些东西发生了变化时我才能更新树,我猜这似乎是降低我的应用程序cpu使用率的关键 剪报: 如果我的应用程序启动,我将用所有正在运行的进程填充树。 请记住,这只调用一次,稍后当应用程序运行时,我收到新进程或通过wmi终止的进程的通知,因此我不需要稍后在计时器中调用以下过

嗯,我正在使用VirtualStringTree创建一种流程管理器

我遇到了麻烦,因为更新树时计时器设置为1000ms(cpu使用率太高,无法让我的应用程序检索大量数据(填充大约20列)

所以我想知道如何构建一种缓存系统,这样只有当某些东西发生了变化时我才能更新树,我猜这似乎是降低我的应用程序cpu使用率的关键

剪报:

如果我的应用程序启动,我将用所有正在运行的进程填充树。 请记住,这只调用一次,稍后当应用程序运行时,我收到新进程或通过wmi终止的进程的通知,因此我不需要稍后在计时器中调用以下过程来更新树

procedure FillTree;
begin
var
  NodeData: PProcessData;
  Node: PVirtualNode;
  ParentNode: PVirtualNode;
  ChildNode: PVirtualNode;
  Process: TProcessItem;
  I : Integer;
begin
   ProcessTree.BeginUpdate;
   for I := 0 to FRunningProcesses.Count - 1 do
  begin
    Process := FRunningProcesses[i];

    NodeData^.pProcessID := ProcessItem.ProcessID;
    NodeData^.pProcessName := ProcessItem.ProcessName;

...
我有一个类,它将检索我想要的所有数据并将其存储到树中,如:

var
  FRunningProcesses: TProcessRunningProcesses;
因此,如果我想枚举所有正在运行的进程,我只需调用它,如下所示:

  // clears all data inside the class and refills everything with the new data... 
  FRunningProcesses.UpdateProcesses;
问题从这里开始,我列举了所有内容,而不仅仅是已经更改的数据,这些数据占用了大量cpu:

procedure TMainForm.UpdateTimerTimer(Sender: TObject);
var
  NodeData: PProcessData;
  Node : PVirtualNode;
  Process: TProcessItem;
  I: Integer;
begin
   for I := 0 to FRunningProcesses.Count - 1 do
   begin
      Application.ProcessMessages;

      Process := FRunningProcesses[I];

      // returns PVirtualNode if the node is found inside the tree
      Node := FindNodeByPID(Process.ProcessID);

      if not(assigned(Node)) then
      exit;

      NodeData := ProcessVst.GetNodeData(Node);

      if not(assigned(NodeData)) then
       exit;

     // now starting updating the tree 
     // NodeData^.pWorkingsSet := Process.WorkingsSet; 
....
基本上,计时器仅用于cpu使用和我可以从以下进程检索的所有内存信息:

  • 私人记忆
  • 工作组
  • 峰值工作集
  • 虚拟大小
  • 页面文件使用
  • 页面文件使用峰值
  • 页面错误
  • Cpu使用率
  • 线程数
  • 句柄计数
  • GDI句柄计数
  • 用户句柄计数
  • 总Cpu时间
  • 用户Cpu时间
  • 内核Cpu时间

因此,我认为,如果上述数据发生了更改,则必须以某种方式对其进行缓存和比较,而不仅仅是想知道如何以及什么最有效?

您只需更新当前可见节点中的数据。 您可以使用
vst.getfirstvisible
vst.getnextvisible
对这些节点进行迭代

第二种方法也很简单。 使用对象而不是记录

对不同的值使用getter。 这些getter查询进程中的值。 也许您需要在这里设置一个限制。仅每秒刷新数据

vst.invalidate
现在,您只需要每秒将vst设置为无效状态

vst.invalidate
这迫使vst重新绘制可见区域

但是,只有当您的数据没有按任何变化的值排序时,所有这些才起作用。 如果这是必要的,你需要更新所有记录,这是你的瓶颈-我想。 记住COM和WMI比纯API慢得多。
避免(慢速)循环,并使用探查器查找慢速部分。

我建议您将VT的节点数据直接指向TProcessItem

专业人士:

  • 摆脱
    FindNodeByPID
    。只需更新
    FRunningProcesses
    ,然后在进程启动时调用
    VT.Refresh
    终止,从
    frunningprocesss
    中删除相应的项。 目前,您在
    FindNodeByPID
    where中有相当昂贵的搜索 循环遍历所有VT节点,检索它们的数据并检查 PID
  • 摆脱你已经拥有的
    Process:=frunningprocesss[I]
    整个TProcessData记录的不必要的数据副本(顺便说一句,即 无论如何都应该这样做,请使用指针)
  • 去掉整个
    //现在开始更新树
  • 通常,通过此更改可以减少多余的实体,这对应用程序更新和调试非常有用
  • 被告:

  • 你必须使VT和FrunningProcess保持同步。但这很简单

  • wmi的性能不好已经不是什么秘密了。另一方面,我不知道有什么api可以告诉你进程是否已死亡或已创建。

    我不想只为已创建或已终止的进程每秒刷新整个列表,并且只将此进程添加或删除到树中,而不是在任何时候。
    现在,任何api都会告诉您进程的创建或终止,除非您想挂接csrss或编写驱动程序并注册PsSetCreateProcessNotifyRoutine,我绝对不能:(@stOrM-那么你需要减慢刷新速度。我还将从读取进程数据的分离线程开始。如果与上一个进程数据存在差异,则更新节点(使用消息同步)否则什么也不做。您也不需要刷新整个VT,也不需要调用
    应用程序。ProcessMessages
    ;它可能会闪烁很多。@daemon_x到目前为止听起来不错……只是不知道如何处理processcollection中的pvirtualnode,我以后需要将它存储在树记录中(也许我现在开始关注:)@stOrM-想法是你将有一个单独的线程,它将迭代你的进程集合,并根据当前值检查最后收集的值。如果你注意到一个更改,你会说:酷,我在第n条记录中的PageFileUsage有一个更改(将出现相应节点的位置),我需要将其刷新给用户。因此,您的工作线程将数据提供给主线程,并调用
    TreeView.InvalidateNode
    ,这将只刷新此节点。我不确定什么是最有效的,但我在这里看到了这么多解决方案。@stOrM-我问了类似的问题,我用关键部分锁解决了这个问题。