【发布时间】:2012-01-17 15:26:38
【问题描述】:
我正在尝试创建一个在 RichTextBox 中搜索和突出显示文本的 c# WinForms 应用程序。我创建了两种搜索方法:一种在 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);
}
}
注意事项:
- 我知道 RTB 类有自己的 find 方法,但发现它比我自己的方法慢得多。
- 我已经阅读了许多关于 BGW 性能的线程,大多数似乎都将使用 Invoke 方法作为原因,但我没有使用任何线程。
- 我知道使用多线程会使其运行速度变慢,但没想到会有这么大的差异。
- 问题不在于
ReportProgress,我已将这一行注释掉。我这样做而不是百分比的原因是计算百分比的计算有很大的不同。这种方式实际上更快 - 另一个用户提供的link 描述了我如何在非 GUI 线程中使用我的 RTB。这似乎表明它不应该是一个问题,但会产生更多的开销,因为它会导致创建消息队列。我不确定这是否会影响我的 foreach 循环中代码的性能。任何有关此事的 cmet 将不胜感激。
【问题讨论】:
-
可能是后台线程的优先级设置得太低了? “基本相同的代码”也不是相同的代码。
-
@GCaiazzo 感谢您的评论。我尝试像这样设置优先级:
System.Diagnostics.Process.GetCurrentProcess().PriorityClass = System.Diagnostics.ProcessPriorityClass.High;但似乎没有什么不同。 (我知道这是一个坏主意,因为线程是池化的。我只是将其作为测试)。当我说基本相同时,我指的是 foreach 循环中的逻辑。这是一样的。我觉得^^ -
我正在查看的代码实际上是一件坏事(tm)。第一个问题是您正在后台线程上创建
Control(RichTextBox)。根据经验,仅在主 UI 线程上创建控件。当您在后台线程上创建Control时,您正在后台执行吨 的废话,而这不应 在后台线程上完成。相反,将一个字符串传递给您的后台线程并让您的后台线程返回突出显示索引,以便您的前台线程可以突出显示后台线程找到的文本块。 -
请记住,UI 的更新速度不会比每 20 毫秒快得多(大约每秒 60 次)——即使是这样,您为什么要这样做?因此,如果您每隔几毫秒就在后台工作人员升级进度,那么您将花费大量时间将数据编组到 UI 线程中——这不是免费的。您可以尝试在后台工作人员中尽可能快地更新数据,然后让 UI Timer 线程每秒获取几次数据——这可能会减少编组量。
标签: c# .net winforms performance backgroundworker