Почему тот же код намного медленнее в моем потоке BackGroundWorker, чем в моем потоке графического интерфейса? - PullRequest
6 голосов
/ 11 декабря 2011

Я пытаюсь создать приложение c # WinForms, которое ищет и выделяет текст в RichTextBox. Я создал два метода поиска: один работает в потоке графического интерфейса, а другой - в BackGroundWorker. Логика в обоих методах практически идентична. Однако код в BGW работает значительно медленнее.

Пожалуйста, смотрите результаты ниже:

0,25 МБ Текстовый файл с поиском по общему ключевому слову: GUI: 2,9 с - BGW: 7,0 с
1 МБ Текстовый файл с поиском по общему ключевому слову: GUI: 14,1 с - BGW: 71,4 с
5 МБ Текстовый файл с поиском по общему ключевому слову: GUI: 172 с - BGW: 1545 с

Мне кажется странным, что соотношение времени, затраченного на эти два метода, не является линейным по отношению к размеру поиска.

Приложение будет использоваться для поиска файлов размером до 10 МБ, поэтому важно, чтобы это было быстро. Я хотел использовать фонового работника, чтобы пользователь мог видеть прогресс и продолжить чтение файла во время поиска.

Пожалуйста, смотрите код для двух методов ниже:

    // 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 Я прокомментировал эту строку. Причина, по которой я делаю это таким образом, а не в процентах, заключается в том, что расчеты для расчета процента имеют большое значение. Так на самом деле быстрее
  • Эта ссылка , предоставленная другим пользователем, описывает, как я использую свой RTB в потоке без графического интерфейса пользователя. Похоже, что это не должно быть проблемой, но это повлечет за собой дополнительные издержки, так как это приведет к созданию очереди сообщений. Я не уверен, повлияет ли это на производительность кода в цикле foreach. Будем весьма благодарны за любые комментарии по этому вопросу.

Ответы [ 3 ]

0 голосов
/ 14 декабря 2011

Не уверен ..., но каждый раз, когда вы вызываете установщик в SelectedRtf, происходит много всего, в том числе получение кодировки строки в юникоде, запись ее в буфер, а затем отправляется целый ряд сообщений 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 голосов
/ 30 августа 2012

Не помещается в комментарии, поэтому я выложу ответ.

Я давно не использовал WinForms, но разве WinForms не должен выдавать ошибку при доступе к элементу пользовательского интерфейса из не-пользовательского кода? Я помню, что мне приходилось делать кучу вещей this.Invoke, но, возможно, специалист по фону работает по-другому.

В любом случае, я предполагаю, что основная часть дополнительного времени будет синхронизироваться с упорядочением потока пользовательского интерфейса для доступа к RichTextBox. Возьмите старый добрый секундомер и измерьте свой код, чтобы увидеть, где находится ботлнек.

Интересно, было бы быстрее разделить текст на куски и использовать несколько потоков - работа с четырьмя ядрами;) - найти все совпадения, а затем, в конце, перейти к потоку пользовательского интерфейса, перебрать все совпадения и выделить текст.

Также должна быть предусмотрена возможность выделять текст только в видимой области экрана, и когда пользователь прокручивает, чтобы выделить дополнительный текст ...

0 голосов
/ 11 декабря 2011

Одной вещью, которая обычно замедляет Winforms, является синхронизация с потоком пользовательского интерфейса. Если ReportProgress делает это (я не знаю, но я предполагаю, что это должно), и вы вызываете это слишком часто (скажем, 100-1000 раз в секунду или более), это замедлит все до полной остановки из-за различных проблем блокировки это произойдет.

Попробуйте удалить все взаимодействия между пользовательским интерфейсом и фоновым потоком, которые у вас есть, и, если это поможет, восстановите взаимодействие, но пусть это происходит гораздо реже, например, 1-100 раз в секунду.

Кроме того, я не уверен, но если вы передаете ссылку на объект управления, он все равно может принадлежать потоку пользовательского интерфейса, и каждое взаимодействие с ним из другого потока может также вызвать проблемы с синхронизацией (и взаимодействие с фактический контроль форм вызовет исключение).

...