为什么我的BackGroundWorker线程中的相同代码比我的GUI线程中的代码慢得多?

时间:2011-12-10 23:20:16

标签: c# .net winforms performance backgroundworker

我正在尝试创建一个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);
        }
    }

注意:

  • 我知道RTB类有自己的find方法,但发现这比我自己的方法慢得多。
  • 我已经阅读了许多关于BGW性能的线程,并且大多数人似乎将Invoke方法的使用作为原因,但我没有使用。
  • 我理解多线程的使用会使它运行得更慢,但并没有预料到这么大的差异。
  • 问题不在ReportProgress我已经评论过这一行。我这样做而不是百分比的原因是计算得出百分比差别很大。实际上这种方式更快
  • 其他用户提供的link描述了我在非GUI线程中使用RTB的方式。它似乎表明它不应该是一个问题,但会产生更多的开销,因为它会导致创建一个消息队列。我不确定这是否会影响我的foreach循环中代码的性能。对此事的任何评论都将不胜感激。

3 个答案:

答案 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线程,遍历所有匹配并突出显示文本。

还应该可以只在屏幕的可见区域上突出显示文本,以及当用户滚动到更高的文本时...