我正在尝试创建一个c#WinForms应用程序,用于搜索和突出显示RichTextBox中的文本。我创建了两种搜索方法:一种在GUI线程中运行,另一种在BackGroundWorker中运行。两种方法中的逻辑基本相同。但是,BGW中的代码运行速度要慢得多。
请参阅以下结果:
0.25MB搜索常用关键字的文本文件:GUI:2.9s - BGW:7.0s
1MB文本文件搜索一个共同的关键字:GUI:14.1s - BGW:71.4s
5MB文本文件搜索一个共同的关键字:GUI:172s - BGW:1545s
我觉得这两种方法所花费的时间之间的关系并不是关于搜索大小的关系。
该应用程序将用于搜索最大10MB的文件,因此这很重要。我想使用后台工作程序,以便用户可以看到进度并在执行搜索时继续读取文件。
请参阅以下两种方法的代码:
// background search thread
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Get the BackgroundWorker that raised this event.
BackgroundWorker worker = sender as BackgroundWorker;
RichTextBox rtb = new RichTextBox();
RichTextBox results = new RichTextBox();
rtb.Rtf = e.Argument as string; //recive text to be searched
int hits = 0; // track number of hits
int pos = 0; // track position in rtb
int i = 0; // trach current line number for progress report
string lowerT = searchTerm.ToLowerInvariant();
string lowerl = "";
int n = 0;
int len = searchTerm.Length;
foreach (string l in rtb.Lines)
{
lowerl = l.ToLowerInvariant();
n = lowerl.IndexOf(lowerT);
if (n > -1)
{
while (n > -1) //if found sterm highlight instances
{
hits++; //incriment hits
//hilight term
rtb.SelectionStart = pos + n;
rtb.SelectionLength = len;
rtb.SelectionBackColor = Color.Yellow;
rtb.SelectionColor = Color.Black;
//find next
n = lowerl.IndexOf(lowerT, n + len);
}
searchRes.Add(pos); // add positon of hit to results list
//add rtb formatted text to results rtb
rtb.SelectionStart = pos;
rtb.SelectionLength = l.Length;
results.SelectedRtf = rtb.SelectedRtf;
results.AppendText(Environment.NewLine);
}
pos += l.Length + 1; //incriment position
//worker.ReportProgress(++i);
}
string[] res = {rtb.Rtf,results.Rtf,hits.ToString()};
e.Result = res;
}
// old non threaded search method
public void OldSearch(string sTerm)
{
int hits = 0; // track number of hits
int pos = 0; // track position in rtb
int oldPos = richTextBox1.SelectionStart; //save current positin in rtb
int oldLen = richTextBox1.SelectionLength;
string lowerT = sTerm.ToLowerInvariant();
sTime = 0;
System.Threading.Timer tmr = new System.Threading.Timer(new TimerCallback(TimerTask), null, 0, 100);
if (sTerm.Length > 0)
{
//clear old search
ReloadFile();
richTextBox4.Clear();
searchRes = new List<int>();
//open results pane
label1.Text = "Searching for \"" + sTerm + "\"...";
splitContainer1.Panel2Collapsed = false;
frmFind.Focus();
frmFind.ShowProgress(true);
foreach (string l in richTextBox1.Lines)
{
string lowerl = l.ToLowerInvariant();
int n = lowerl.IndexOf(lowerT);
if (n > -1)
{
while (n > -1) //if found sterm highlight instances
{
hits++; //incriment hits
//hilight term
richTextBox1.SelectionStart = pos + n;
richTextBox1.SelectionLength = sTerm.Length;
richTextBox1.SelectionBackColor = Color.Yellow;
richTextBox1.SelectionColor = Color.Black;
//find next
n = lowerl.IndexOf(lowerT, n + sTerm.Length);
}
searchRes.Add(pos);
richTextBox1.SelectionStart = pos;
richTextBox1.SelectionLength = l.Length;
richTextBox4.SelectedRtf = richTextBox1.SelectedRtf;
richTextBox4.AppendText(Environment.NewLine);
}
pos += l.Length + 1; //incriment position
}
tmr.Dispose();
float time = (float)sTime / 10;
label1.Text = "Search for \"" + sTerm + "\": Found " + hits + " instances in " + time + " seconds.";
richTextBox4.SelectionStart = 0;
richTextBox1.SelectionStart = oldPos;
richTextBox1.SelectionLength = oldLen;
richTextBox1.Focus();
frmFind.ShowProgress(false);
}
}
注意:
ReportProgress
我已经评论过这一行。我这样做而不是百分比的原因是计算得出百分比差别很大。实际上这种方式更快答案 0 :(得分:0)
通常减慢Winforms的一件事是与UI线程同步。如果ReportProgress这样做(我不知道,但我猜不得不这样做)并且你经常调用它(比如每秒100-1000次或更多),它会减慢一切因为停止将发生的各种阻塞问题。
尝试删除您拥有的UI和后台线程之间的任何交互,如果有帮助,请恢复交互,但让它更少发生,例如每秒1-100次。
另外,我不确定,但是如果您传递对控件对象的引用,它可能仍然由UI线程拥有,并且每次与另一个线程的交互也可能导致同步问题(与实际表单控件的交互会引发异常)。
答案 1 :(得分:0)
不确定...,但每次在SelectedRtf上调用setter时,会发生很多事情,包括获取字符串的unicode编码,将其写入缓冲区然后发送大量Windows消息
首先,如果您可以在不访问RTF搜索框的情况下重新设计算法以尽可能多地执行,然后批量突出显示,则可能会提高性能。
至于为什么它更慢...... RTF框是在后台线程上创建的。可能是当他们发送消息并且没有消息循环来处理它们时,会有延迟。或者也许有一些编组回到正确的SynchronizationContext发生,花费时间。不确定。
描述您自己的代码和.NET Framework代码的分析器应该告诉您。
public string SelectedRtf
{
get
{
this.ForceHandleCreate();
return this.StreamOut(32770);
}
set
{
this.ForceHandleCreate();
if (value == null)
value = "";
this.StreamIn(value, 32770);
}
}
private void StreamIn(string str, int flags)
{
if (str.Length == 0)
{
if ((32768 & flags) != 0)
{
this.SendMessage(771, 0, 0);
this.ProtectedError = false;
}
else
this.SendMessage(12, 0, "");
}
else
{
int length = str.IndexOf(char.MinValue);
if (length != -1)
str = str.Substring(0, length);
byte[] buffer = (flags & 16) == 0 ? Encoding.Default.GetBytes(str) : Encoding.Unicode.GetBytes(str);
this.editStream = (Stream) new MemoryStream(buffer.Length);
this.editStream.Write(buffer, 0, buffer.Length);
this.editStream.Position = 0L;
this.StreamIn(this.editStream, flags);
}
}
private void StreamIn(Stream data, int flags)
{
if ((flags & 32768) == 0)
System.Windows.Forms.UnsafeNativeMethods.SendMessage(new HandleRef((object) this, this.Handle), 1079, 0, new System.Windows.Forms.NativeMethods.CHARRANGE());
try
{
this.editStream = data;
if ((flags & 2) != 0)
{
long position = this.editStream.Position;
byte[] numArray = new byte[RichTextBox.SZ_RTF_TAG.Length];
this.editStream.Read(numArray, (int) position, RichTextBox.SZ_RTF_TAG.Length);
string @string = Encoding.Default.GetString(numArray);
if (!RichTextBox.SZ_RTF_TAG.Equals(@string))
throw new ArgumentException(System.Windows.Forms.SR.GetString("InvalidFileFormat"));
this.editStream.Position = position;
}
System.Windows.Forms.NativeMethods.EDITSTREAM editstream = new System.Windows.Forms.NativeMethods.EDITSTREAM();
int num1 = (flags & 16) == 0 ? 5 : 9;
int num2 = (flags & 2) == 0 ? num1 | 16 : num1 | 64;
editstream.dwCookie = (IntPtr) num2;
editstream.pfnCallback = new System.Windows.Forms.NativeMethods.EditStreamCallback(this.EditStreamProc);
this.SendMessage(1077, 0, int.MaxValue);
if (IntPtr.Size == 8)
{
System.Windows.Forms.NativeMethods.EDITSTREAM64 editstreaM64 = this.ConvertToEDITSTREAM64(editstream);
System.Windows.Forms.UnsafeNativeMethods.SendMessage(new HandleRef((object) this, this.Handle), 1097, flags, editstreaM64);
editstream.dwError = this.GetErrorValue64(editstreaM64);
}
else
System.Windows.Forms.UnsafeNativeMethods.SendMessage(new HandleRef((object) this, this.Handle), 1097, flags, editstream);
this.UpdateMaxLength();
if (this.GetProtectedError())
return;
if (editstream.dwError != 0)
throw new InvalidOperationException(System.Windows.Forms.SR.GetString("LoadTextError"));
this.SendMessage(185, -1, 0);
this.SendMessage(186, 0, 0);
}
finally
{
this.editStream = (Stream) null;
}
}
答案 2 :(得分:0)
不适合评论,所以我会发一个答案。
我已经多年没有使用过WinForms了,但是WinForms不应该因为从非UI代码访问UI元素而抛出错误?我记得不得不做一堆this.Invoke
的东西,但也许背景工作者以不同的方式处理事情。
无论如何,我的猜测是额外时间的主要部分是与UI线程同步以便访问RichTextBox。带出好的旧秒表并测量你的代码,看看botleneck在哪里。
我想知道将文本分成块并使用多个线程更快 - 四核工作;) - 找到所有匹配然后最后转到UI线程,遍历所有匹配并突出显示文本。
还应该可以只在屏幕的可见区域上突出显示文本,以及当用户滚动到更高的文本时...