2012-01-25 6 views
6

Dado el siguiente código de ejemplo:Prevenir un cuadro de texto de retraso debido a las actualizaciones rápidas

new Thread(() => 
{ 
    for(int i = 0; i < 10000; i++) 
    { 
     Invoke((MethodInvoker)() => 
     { 
      myTextBox.Text += DateTime.Now.ToString() + "\r\n"; 
      myTextBox.SelectedIndex = myTextBox.Text.Length; 
      myTextBox.ScrollToCarat(); 
     }); 
    } 
}).Start();

Cuando se ejecuta este código, después del bucle y el hilo por terminado, el cuadro de texto se sigue actualizando (presumiblemente a causa de buffer Invoca). Mi aplicación utiliza una lógica similar para llenar un cuadro de texto, y estoy teniendo el mismo problema.

Mi pregunta es: ¿Cómo puedo llenar este cuadro de texto lo más rápido posible, seguir desplazándome hacia abajo cada vez, y aún así reducir/eliminar este retraso?

+2

¿Puedes leer tan rápido? No soy. Simplemente actualice el cuadro de texto cada x segundos en lugar de cada marca del reloj. –

+0

Esto fue solo un ejemplo de cómo reproducir el problema (aunque sea un poco extremo). De hecho, estoy leyendo de una transmisión, por lo que las actualizaciones pueden ser rápidas o lentas. – qJake

+0

Todavía mi pregunta y sugerencia siguen siendo las mismas. UI es para acomodar al usuario. –

Respuesta

7

Hay algunas opciones que puede tomar aquí. En primer lugar, puede configurar el doble búfer en el formulario, que terminará dibujando todas las actualizaciones en un mapa de bits subyacente, que luego muestra la imagen recién dibujada (en lugar de dibujar los Controles de forma individual en un objeto gráfico). Vi un aumento de velocidad del 50% con este método. Tirar esto en el constructor:

this.SetStyle(
    ControlStyles.AllPaintingInWmPaint | 
    ControlStyles.UserPaint | 
    ControlStyles.DoubleBuffer,true); 

La otra cosa a tener en cuenta es que la concatenación de cadenas es lento para grandes cantidades de datos. Es mejor usar un StringBuilder para compilar los datos y luego simplemente mostrarlos usando StringBuilder.ToString (aunque es aún mejor escalonar las actualizaciones, tal vez una vez cada 100 iteraciones). En mi máquina, simplemente cambiándolo para agregarlo a StringBuilder, pasó de 2.5 minutos a ejecutarse a través de iteraciones de 10k a aproximadamente 1.5 minutos. Mejor, pero aún lento.

new System.Threading.Thread(() => 
{ 
    for(int i = 0; i < 10000; i++) 
    { 
     sb.AppendLine(DateTime.Now.ToString()); 
     Invoke((Action)(() => 
     { 
      txtArea.Text = sb.ToString(); 
      txtArea.SelectionStart = txtArea.Text.Length; 
      txtArea.ScrollToCaret(); 
     })); 
    } 
}).Start(); 

Finalmente, justo probado a cabo escalonamiento (arrojó una sola condicional en el código anterior, justo antes de la llamada Invoke), y se terminó en 2 segundos. Dado que estamos usando StringBuilder para construir realmente la cadena, aún conservamos todos los datos, pero ahora solo tenemos que hacer las actualizaciones 100 veces en vez de 10k veces.

Entonces, ¿cuáles son sus opciones? Dado que se trata de una aplicación WinForm, puede utilizar uno de los muchos objetos Timer para realizar la actualización de la interfaz de usuario para ese control en particular, o puede mantener un contador de cuántas "lecturas" o "actualizaciones" de los datos subyacentes (en su caso, una secuencia) y solo actualiza la interfaz de usuario en X cantidad de cambios. Utilizar tanto la opción StringBuilder como las actualizaciones escalonadas es probablemente el camino a seguir.

+0

En pocas palabras, esto es lo que hice. Usé un StringBuilder y guardé las actualizaciones a aproximadamente 1-200ms en un hilo separado, y ahora funciona mucho mejor. ¡Gracias! – qJake

1

La estrategia de actualización de la interfaz de usuario es la tarea más difícil en las aplicaciones de procesamiento de datos. uso el siguiente patrón: trabajo

  1. Trabajando flujo está realizando y almacena los resultados en los resultados de almacenamiento
  2. hilo de interfaz de usuario de actualización está agregando los resultados y las actualizaciones de la interfaz de usuario si es necesario
3

Usted podría intentar búfer : En lugar de escribir directamente en TextBox y luego desplazarse, escriba a StringBuilder (¡asegúrese de averiguar cómo hacerlo de una manera segura para la ejecución de subprocesos!) Y tenga un hilo separado flush al TextBox en un intervalo fijo (digamos cada segundo).

0

Uso System.Windows.Forms.Timer para escribir por lotes en cuadros de texto en trozos de 50 ms. Utilizo una clase RingBuffer segura para subprocesos como el búfer entre los subprocesos de escritura y el subproceso de temporizador de formulario (subproceso ui). No puedo darle el código para eso, pero puede reemplazarlo con una cola con bloqueos a su alrededor, o tal vez una de las clases de colección simultáneas.

Ajusta para satisfacer tus necesidades.

/// <summary> 
/// Ferries writes from a non-UI component to a TextBoxBase object. The writes originate 
/// on a non-UI thread, while the destination TextBoxBase object can only be written 
/// from the UI thread. 
/// 
/// Furthermore, we want to batch writes in ~50 ms chunks so as to write to the UI as little as 
/// possible. 
/// 
/// This classes uses a Forms Timer (so that the timer fires from the UI thread) to create 
/// write chunks from the inter-thread buffer to write to the TextBoxBase object. 
/// </summary> 
public class TextBoxBuffer 
{ 
    private RingBuffer<string> buffer; 

    private TextBoxBase textBox; 

    private System.Windows.Forms.Timer formTimer; 

    StringBuilder builder; 

    public TextBoxBuffer(TextBoxBase textBox) 
    { 
     this.textBox = textBox; 

     buffer = new RingBuffer<string>(500); 

     builder = new StringBuilder(500); 

     this.formTimer = new System.Windows.Forms.Timer(); 
     this.formTimer.Tick += new EventHandler(formTimer_Tick); 
     this.formTimer.Interval = 50; 
    } 

    public void Start() 
    { 
     this.formTimer.Start(); 
    } 

    public void Shutdown() 
    { 
     this.formTimer.Stop(); 
     this.formTimer.Dispose(); 
    } 

    public void Write(string text) 
    { 
     buffer.EnqueueBlocking(text); 
    } 

    private void formTimer_Tick(object sender, EventArgs e) 
    { 
     while(WriteChunk()) {} 
     Trim(); 
    } 

    /// <summary> 
    /// Reads from the inter-thread buffer until 
    /// 1) The buffer runs out of data 
    /// 2) More than 50 ms has elapsed 
    /// 3) More than 5000 characters have been read from the buffer. 
    /// 
    /// And then writes the chunk directly to the textbox. 
    /// </summary> 
    /// <returns>Whether or not there is more data to be read from the buffer.</returns> 
    private bool WriteChunk() 
    { 
     string line = null; 
     int start; 
     bool moreData; 

     builder.Length = 0; 
     start = Environment.TickCount; 
     while(true) 
     { 
      moreData = buffer.Dequeue(ref line, 0); 

      if(moreData == false) { break; } 

      builder.Append(line); 

      if(Environment.TickCount - start > 50) { break; } 
      if(builder.Length > 5000) { break; } 
     } 

     if(builder.Length > 0) 
     { 
      this.textBox.AppendText(builder.ToString()); 
      builder.Length = 0; 
     } 

     return moreData; 
    } 

    private void Trim() 
    { 
     if(this.textBox.TextLength > 100 * 1000) 
     { 
      string[] oldLines; 
      string[] newLines; 
      int newLineLength; 

      oldLines = this.textBox.Lines; 
      newLineLength = oldLines.Length/3; 

      newLines = new string[newLineLength]; 

      for(int i = 0; i < newLineLength; i++) 
      { 
       newLines[i] = oldLines[oldLines.Length - newLineLength + i]; 
      } 

      this.textBox.Lines = newLines; 
     } 
    } 
} 
Cuestiones relacionadas