我正在编写一个解析非常大的日志文件的应用程序,以便用户可以以树视图格式查看内容。我使用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<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();
}
));
}
}
不知道为什么我将Refresh和DoEvents留在那里。一点测试其他评论...
答案 0 :(得分:1)
Invoke/BeginInvoke
在内部使用PostMessage
来封送从任意线程到UI线程的请求。 BeginInvoke
将使用需要由UI线程处理的消息充斥您的Windows消息队列,同时伴随着您禁用树重绘的事实,您添加的每个节点都可能影响您与之交互的能力。树正在加载。
一种选择是将多个更新一起批处理,然后发送它们批量更新树。因此,解析文件并使用每100个或一些节点更新树,而不是每次更新1个。
更新:编辑批量添加节点后,我建议如下。
1-相反使用Invoke
而不是BeginInvoke
,否则队列在更新树时填满,然后一旦树被更新,下一千个节点就可以插入了,这使你正确回到你在哪里。
2-插入每个批次后几百毫秒睡眠,以便有一段时间UI可以响应。您可以使用此功能,这将平衡性能与用户体验。较长的睡眠会让用户感觉更敏感,但最终会花费更长的时间来加载所有数据。
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);
}
}
答案 1 :(得分:1)
你要做的第一件事就是在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中,并以较慢的速率传递该集合。
答案 2 :(得分:0)
你尝试了Application.Doevents()
方法吗?
答案 3 :(得分:0)
我没有使用分析器运行您的代码,但如果您认为缓慢的I / O作为botleneck,您可以尝试 MemoryMappedFile from .NET 4.0。
因此,不是从磁盘中逐行搜索和读取,而是可以访问内存中的相同数据而不是磁盘IO。