代码之家  ›  专栏  ›  技术社区  ›  Carlos

在解析非常大的日志文件时保持UI的响应性

  •  1
  • Carlos  · 技术社区  · 16 年前

    我正在编写一个解析非常大的日志文件的应用程序,这样用户就可以看到树视图格式的内容。我使用了BackGroundWorker来读取文件,当它解析每条消息时,我使用BeginInvoke让GUI线程向我的treeview添加一个节点。不幸的是,有两个问题:

    • 在解析文件时,树视图对单击或滚动没有响应。我希望用户能够在解析文件时检查(即展开)节点,这样他们就不必等待整个文件完成解析。

    下面是表单中的代码:

    private void btnChangeDir_Click(object sender, EventArgs e)
    {
        OpenFileDialog browser = new OpenFileDialog();
    
        if (browser.ShowDialog() == DialogResult.OK)
        {
            tbSearchDir.Text = browser.FileName;
            BackgroundWorker bgw = new BackgroundWorker();
            bgw.DoWork += (ob, evArgs) => ParseFile(tbSearchDir.Text);
            bgw.RunWorkerAsync();
        }
    }
    
    private void ParseFile(string inputfile)
    {
        FileStream logFileStream = new FileStream(inputfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
        StreamReader LogsFile = new StreamReader(logFileStream);
    
        while (!LogsFile.EndOfStream)
        {
            string Msgtxt = LogsFile.ReadLine();
            Message msg = new Message(Msgtxt.Substring(26)); //Reads the text into a class with appropriate members
            AddTreeViewNode(msg);
        }
    }
    
    private void AddTreeViewNode(Message msg)
    {
        TreeNode newNode = new TreeNode(msg.SeqNum);
    
        BeginInvoke(new Action(() =>
                                   {
                                       treeView1.BeginUpdate();
                                       treeView1.Nodes.Add(newNode);
                                       treeView1.EndUpdate();
                                       Refresh();
                                   }
                        )); 
    }
    

    需要改变什么?

    编辑

    New code, to replace the last function above:
            List<TreeNode> nodeQueue = new List<TreeNode>(1000);
    
            private void AddTreeViewNode(Message msg)
            {
                TreeNode newNode = new TreeNode(msg.SeqNum);
    
                nodeQueue.Add(newNode);
    
                if (nodeQueue.Count == 1000)
                {
                    var buffer = nodeQueue.ToArray();
                    nodeQueue.Clear();
                    BeginInvoke(new Action(() =>
                                               {
                                                   treeView1.BeginUpdate();
                                                   treeView1.Nodes.AddRange(buffer);
                                                   treeView1.EndUpdate();
                                                   Refresh();
                                                   Application.DoEvents();
                                               }
                                    ));
                }
            }
    

    不知道我为什么把刷新和事件放在那里。测试一下其他评论。。。

    4 回复  |  直到 16 年前
        1
  •  1
  •   Chris Taylor    16 年前

    Invoke/BeginInvoke 使用 PostMessage 在内部将请求从任意线程封送到UI线程。 BeginInvoke

    一种选择是将多个更新批处理在一起,然后将它们批量发送到更新树。因此,解析文件并用每100个或某些节点更新树,而不是一次更新1个节点。

    更新:在您的编辑添加批量节点后,我建议如下。

    1-使用 Invoke 否则,当树被更新时,队列会被填满,然后一旦树被更新,接下来的1000个节点就可以被插入,这会让你回到原来的位置。

        private void AddTreeViewNode(Message msg) 
        { 
            TreeNode newNode = new TreeNode(msg.SeqNum); 
    
            nodeQueue.Add(newNode); 
    
            if (nodeQueue.Count == 1000) 
            { 
                var buffer = nodeQueue.ToArray(); 
                nodeQueue.Clear(); 
                Invoke(new Action(() => 
                    { 
                        treeView1.BeginUpdate(); 
                        treeView1.Nodes.AddRange(buffer); 
                        treeView1.EndUpdate(); 
                    }));
                System.Threading.Thread.Sleep(500); 
            } 
        }
    
        2
  •  1
  •   Hans Passant    16 年前

    首先要做的是在TreeView上启用双缓冲,这样它就会停止闪烁。从Vista开始就支持它,但不幸的是Windows窗体不支持它。向项目中添加一个新类并粘贴如下所示的代码。编译。将工具箱顶部的控件放到窗体上。

    using System;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;
    
    class BufferedTreeView : TreeView {
        protected override void OnHandleCreated(EventArgs e) {
            base.OnHandleCreated(e);
            IntPtr style = (IntPtr)TVS_EX_DOUBLEBUFFER;
            SendMessage(this.Handle, TVM_SETEXTENDEDSTYLE, (IntPtr)style, (IntPtr)style);
        }
        // P/Invoke:
        private const int TVS_EX_DOUBLEBUFFER = 0x004;
        private const int TVM_SETEXTENDEDSTYLE = 0x1100 + 44;
        [DllImport("user32.dll")]
        private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
    }
    

    保持UI响应需要重新设计ParseFile()方法。如前所述,它调用BeginInvoke()的频率太高。这使得UI线程充斥着它无法跟上的请求。它不再逃避正常的职责,比如绘画和鼠标点击。

    这是白费力气,人眼无法感知以每秒25次以上的速度发生的更新。将数据存储在一个集合中,开始遍历并以慢得多的速度传递该集合。

        3
  •  0
  •   Luiscencio    16 年前

    你试过吗 Application.Doevents()

        4
  •  0
  •   Andrey Taptunov    16 年前

    我没有用profiler运行您的代码,但是如果您认为慢I/O是botleneck,您可以试试 MemoryMappedFile from .NET 4.0 .