2011-12-10 30 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進一步文字...

相關問題