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

我正在编写一个解析非常大的日志文件的应用程序,以便用户可以以树视图格式查看内容。 我已经使用BackGroundWorker来读取文件,并且在解析每条消息时,我使用BeginInvoke来获取GUI线程以将节点添加到我的树视图中。 不幸的是,有两个问题:

  • 在解析文件时,树视图无法响应点击或滚动。 我希望用户能够在文件解析时检查(即展开)节点,这样他们就不必等待整个文件完成解析。
  • 每次添加新节点时,树视图都会闪烁。

这是表单中的代码:

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 nodeQueue = new List(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(); } )); } } 

不知道为什么我把Refresh和DoEvents留在那里。 一点测试其他评论……

Invoke/BeginInvoke内部使用PostMessage将来自任意线程的请求PostMessage送到UI线程。 BeginInvoke将使用需要由UI线程处理的消息充斥您的Windows消息队列,同时伴随着您禁用树重绘的事实,您添加的每个节点都可能影响您与树交互的能力。加载。

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

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

1-而是使用Invoke不是BeginInvoke ,否则队列在更新树时填满,然后一旦树被更新,下一千个节点就可以插入,这会让你回到原地。

2-插入每个批次后几百毫秒睡眠,以便有一个UI可以响应的时间段。 您可以使用此function,这将平衡性能与用户体验。 较长的睡眠会让用户感觉更敏感,但最终会花费更长的时间来加载所有数据。

3-请注意,如果总计数不是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); } } 

你要做的第一件事就是在TreeView上启用双缓冲,这样它就会停止闪烁。 这是自Vista以来的支持,但遗憾的是不是由Windows Forms支持的。 在项目中添加一个新类并粘贴下面显示的代码。 编译。 将控件从工具箱顶部拖放到表单上。

 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次的速度发生的更新。 将数据存储在集合BeginInvoke中,并以较慢的速率传递该集合。

你尝试过Application.Doevents()方法吗?

我没有使用分析器运行您的代码,但如果您认为缓慢的I / O作为botleneck,您可以尝试从.NET 4.0中的MemoryMappedFile 。

因此,不是从磁盘中逐行搜索和读取,而是可以访问内存中的相同数据而不是磁盘IO。