2011-12-10 15 views
6

Estoy tratando de crear una aplicación WinForms C# que busca y resalta texto en un RichTextBox. Creé dos métodos de búsqueda: uno que se ejecuta en el hilo de la GUI y otro que se ejecuta en BackGroundWorker. La lógica en ambos métodos es esencialmente idéntica. Sin embargo, el código en el BGW se ejecuta considerablemente más lento.¿Por qué el mismo código es mucho más lento en mi subproceso BackGroundWorker que en mi subproceso GUI?

Por favor, vea los resultados a continuación:

archivo de texto 0.25MB buscar una palabra clave común: interfaz gráfica de usuario: 2.9s - BGW: 7.0s
archivo de texto de 1 MB buscar una palabra clave común: interfaz gráfica de usuario: 14.1s - BGW: 71.4 s
archivo de texto 5MB buscar una palabra clave común: interfaz gráfica de usuario: 172s - BGW: 1545s

parece extraño que la relación entre el tiempo necesario para que los dos métodos no se marítimo con respecto al tamaño de la búsqueda.

La aplicación se usará para buscar archivos de hasta 10MB de tamaño por lo que es importante que esto sea rápido. Quería utilizar un trabajador de fondo para que el usuario pudiera ver el progreso y continuar leyendo el archivo mientras se realizaba la búsqueda.

Por favor, vea el código de los dos métodos siguientes:

// 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); 
     } 
    } 

NOTAS:

  • sé que la clase RTB tiene su propio método de búsqueda, pero encontrado que esto es considerablemente más lento que mi propia método.
  • He leído varios temas relacionados con el rendimiento de BGW y la mayoría parecen ubicar el uso de métodos Invoke como la causa, pero no utilizo ninguno.
  • Entiendo que el uso de varios hilos hará que funcione más lento, pero no esperaba tanta diferencia.
  • El problema no es con ReportProgress que he comentado esta línea. La razón por la que lo hago de esta manera, en lugar de como porcentaje, es que el cálculo para calcular el porcentaje hizo una gran diferencia. En realidad es más rápido de esta manera
  • Este link proporcionado por otro usuario describe cómo estoy usando mi RTB en un hilo que no es de GUI. Parece sugerir que no debería ser un problema, pero incurrirá en más gastos generales, ya que causará la creación de una cola de mensajes. No estoy seguro de si esto afectará el rendimiento del código dentro de mi ciclo foreach. Cualquier comentario sobre el tema sería muy apreciado.
+0

Tal vez la prioridad del subproceso de fondo es demasiado bajo? También "código esencialmente idéntico" no es código idéntico. – GCaiazzo

+0

@GCaiazzo Gracias por el comentario. Intenté establecer la prioridad de esta manera: 'System.Diagnostics.Process.GetCurrentProcess(). PriorityClass = System.Diagnostics.ProcessPriorityClass.High;' pero no pareció marcar la diferencia. (Entiendo que esta es una mala idea, ya que el hilo se mezcla. Lo acabo de hacer como prueba). Cuando dije esencialmente idéntico me refería a la lógica en el ciclo foreach. Que es lo mismo Creo que ^^ – mfa

+0

El código que estoy viendo es en realidad una mala cosa (tm). El primer problema es que está creando un 'Control' (' RichTextBox') en una cadena de fondo. Como regla general, SÓLO crea un control en el hilo de la interfaz de usuario principal. Cuando creas un 'Control' en un hilo de fondo, estás haciendo un _ton_ de basura en el fondo que no debería hacerse en un hilo de fondo. En su lugar, pase una cadena a su hilo de fondo y haga que su hilo de fondo vuelva a resaltar los índices para que su hilo de primer plano pueda resaltar los bloques de texto que encontró el hilo de fondo. –

Respuesta

0

Una cosa que generalmente ralentiza las formas de Win hacia abajo es la sincronización con el subproceso de interfaz de usuario. Si ReportProgress hace eso (no lo sé pero supongo que tiene que hacerlo) y lo llama con demasiada frecuencia (digamos 100-1000 veces por segundo o más) se ralentizará todo debido a varios problemas de bloqueo eso ocurrirá

Intenta eliminar la interacción entre la interfaz de usuario y el hilo de fondo que tengas y, si eso ayuda, restablece la interacción pero deja que ocurra con menos frecuencia, como 1-100 veces por segundo.

Además, no estoy seguro, pero si está pasando una referencia a un objeto de control, aún podría ser propiedad del subproceso de UI y cada interacción con él desde otro subproceso también podría causar problemas de sincronización (y la interacción con un control de formularios real lanzaría una excepción).

+0

Gracias por su respuesta. He intentado con lo que sugirió, pero lamentablemente el rendimiento no ha mejorado realmente. Eliminé todas las referencias a objetos de subprocesos de GUI dentro del ciclo ('worker.ReportProgress()' y 'searchRes.Add()'). Esto es lo que estoy pasando al hilo 'string parsedText = richTextBox1.Rtf; backgroundWorker1.RunWorkerAsync (parsedText); '' parsedText' es una variable global de mi objeto 'Form1'. – mfa

0

No estoy seguro ..., pero cada vez que llamas al colocador en SelectedRtf, suceden muchas cosas, incluida la obtención de una codificación unicode de la cadena, escribirla en un búfer y luego enviar una gran cantidad de mensajes de Windows.

En primer lugar, si puede rediseñar el algoritmo para hacer tanto como sea posible sin tener que acceder al cuadro de búsqueda RTF y luego agregar el resaltado, probablemente mejore el rendimiento.

En cuanto a por qué es más lento ... las cajas RTF se crean en una cadena de fondo. Puede ser que cuando envían mensajes y no hay un bucle de mensaje para procesarlos, hay un retraso. O tal vez haya una reorganización hacia el evento SynchronizationContext correcto que toma el tiempo. No es seguro.

Sin embargo, un perfil que perfila su propio código y el código de .NET Framework debería decirle.

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

No cabe en un comentario, así que voy a publicar una respuesta.

No he usado WinForms por años, pero ¿no debería WinForms arrojar un error al acceder a un elemento UI desde un código que no sea UI? Recuerdo tener que hacer un montón de cosas this.Invoke, pero tal vez el backgroundworker maneja las cosas de manera diferente.

De todos modos, mi suposición es que la mayor parte del tiempo extra se sincronizará con el subproceso de la interfaz de usuario para acceder al RichTextBox. Saca el viejo cronómetro y mide tu código para ver dónde está el cuello.

Me pregunto si sería más rápido separar el texto en pedazos y usar múltiples hilos - trabajo de cuatro núcleos;) - para encontrar todas las coincidencias y luego al final ir al hilo de UI, repetir todas las coincidencias y resaltar el texto.

También debe ser posible solamente el texto resaltado en el área visible de la pantalla y cuando el usuario se desplaza a higlight más texto ...

Cuestiones relacionadas