2011-12-10 31 views
6

我正在尝试创建一个c#WinForms应用程序,它可以在RichTextBox中搜索并突出显示文本。我创建了两种搜索方法:一种在GUI线程中运行,另一种在BackGroundWorker中运行。两种方法的逻辑基本相同。但是,BGW中的代码运行速度要慢得多。为什么在我的BackGroundWorker线程中相同的代码比在我的GUI线程中慢得多?

请参阅下面的结果:

0.25 MB文本文件搜索一些常见的关键字:GUI:2.9s - BGW:7.0s
1MB文本文件搜索一些常见的关键字:GUI:14.1s - BGW:71.4小号
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类都有自己的查找方法,但发现这是比我自己的要慢得多方法。
  • 我已经阅读了许多有关BGW性能的主题,并且大多数网站似乎都使用Invoke方法作为原因,但我没有使用任何方法。
  • 我明白多线程的使用会让它运行速度变慢,但并没有期待这么大的差别。
  • 问题不在ReportProgress我已经评论了这条线。我这样做的原因,而不是作为一个百分比是计算,以确定百分比有很大的不同。它实际上更快这种方式
  • link由另一位用户提供描述我如何在非GUI线程中使用我的RTB。它似乎暗示它不应该是一个问题,但会招致更多的开销,因为它会导致创建消息队列。我不确定这是否会影响我的foreach循环中的代码的性能。对此事的任何意见将不胜感激。
+0

也许后台线程的优先级设置过低屏幕的可见区域? “基本相同的代码”也不是相同的代码。 – GCaiazzo

+0

@GCaiazzo感谢您的评论。我尝试设置优先级,如下所示:'System.Diagnostics.Process.GetCurrentProcess()。PriorityClass = System.Diagnostics.ProcessPriorityClass.High;'但它似乎没有什么区别。 (我知道这是一个坏主意,因为线程是汇集的,我只是做了一个测试)。当我说基本相同时,我指的是foreach循环中的逻辑。这是一样的。我认为^^ – mfa

+0

我正在看的代码实际上是一件坏事(tm)。第一个问题是你正在后台线程上创建一个'Control'('RichTextBox')。作为一个经验法则,只需在主UI线程上创建一个控件。当你在后台线程上创建一个'Control'时,你在背景中做了一堆废话,不应该在后台线程上完成。相反,将字符串传递给后台线程,让后台线程返回突出显示的索引,以便前台线程可以突出显示后台线程找到的文本块。 –

回答

0

一般来说,减缓WinForms的一件事是与UI线程同步。如果ReportProgress这样做(我不知道,但我猜它必须),并且你经常调用它(例如每秒100-1000次),由于存在各种阻塞问题,它会使所有事情减速到停滞状态那将会发生。

尝试删除UI和后台线程之间的任何交互,如果有帮助,恢复交互但让其发生得更少,例如每秒1-100次。另外,我不确定,但是如果你传递一个控件对象的引用,它可能仍然由UI线程拥有,并且从另一个线程与它进行的每次交互也可能导致同步问题(以及与一个实际的表单控件会抛出异常)。

+0

谢谢你的回答。我已经尝试了你的建议,但不幸的是,性能并没有真正提高。我删除了循环内所有对GUI线程对象的引用('worker.ReportProgress()'和'searchRes.Add()')。这是我传递给线程'string parsedText = richTextBox1.Rtf; backgroundWorker1.RunWorkerAsync(parsedText);''parsedText'是我的Form1对象的全局变量。 – mfa

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; 
    } 
} 
0

不适合发表评论,所以我会发表一个答案。

我没有使用WinForms的年龄,但不应该WinForms抛出一个错误,从非UI代码访问UI元素?我记得不得不做一堆this.Invoke的东西,但也许背景工作人员处理事情的方式不同。

无论如何,我的猜测是,额外时间的主要块将与UI线程同步,以便访问RichTextBox。拿出好的旧秒表,测量你的代码,看看botleneck在哪里。

我不知道如果将文本分成块并使用多个线程 - 四核心工作;) - 找到所有匹配,然后最终转到UI线程,迭代所有匹配并突出显示文本。

它也应该可以只强调文本上,并且当用户滚动到higlight进一步文字...

相关问题