2012-01-25 13 views
6

Dato il seguente codice esempio:Impedire una casella di testo da ritardo dovuto aggiornamenti veloci

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();

Quando si esegue questo codice, dopo il ciclo e filo terminare, la casella di testo è ancora aggiornamento (presumibilmente a causa di tamponata Richiama). La mia applicazione utilizza una logica simile per riempire una casella di testo e sto riscontrando lo stesso problema.

La mia domanda è: come posso riempire questa casella di testo il più velocemente possibile, ancora scorrere verso il basso ogni volta, e ancora ridurre/eliminare questo ritardo?

+2

Sei in grado di leggere così veloce? Io non sono. Basta aggiornare la casella di testo ogni x secondi invece di ogni spunta di orologio. –

+0

Questo era solo un esempio di come riprodurre il problema (anche se un po 'estremo). In realtà sto leggendo da un flusso, quindi gli aggiornamenti potrebbero arrivare veloci o lenti. – qJake

+0

Ancora la mia domanda e il mio suggerimento rimangono gli stessi. L'interfaccia utente è per ospitare l'utente. –

risposta

7

Ci sono alcune opzioni che puoi portare qui. Innanzitutto, è possibile impostare il doppio buffering sul modulo, che finirà per disegnare tutti gli aggiornamenti su una bitmap sottostante, che quindi visualizzerà l'immagine appena disegnata (anziché i singoli controlli di disegno su un oggetto grafico). Ho visto un aumento della velocità del 50% con questo metodo. Gettare questo nel costruttore:

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

L'altra cosa da tenere a mente è che la concatenazione di stringhe è lento per grandi quantità di dati. È meglio usare StringBuilder per creare i dati e poi mostrarli usando StringBuilder.ToString (anche se è ancora meglio scaglionare gli aggiornamenti, forse una volta ogni 100 iterazioni). Sulla mia macchina, basta cambiarlo per aggiungerlo allo StringBuilder, è passato da 2,5 minuti per passare attraverso iterazioni da 10k a circa 1,5 minuti. Meglio, ma ancora 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(); 

Infine, appena testato su sfalsamento (gettato un singolo condizionale nel codice sopra, proprio prima che la chiamata Invoke), ed è finito in 2 secondi. Dato che stiamo usando StringBuilder per creare effettivamente la stringa, conserviamo tutti i dati, ma ora dobbiamo fare gli aggiornamenti solo 100 volte rispetto a 10k volte.

Quindi, quali sono le opzioni? Poiché si tratta di un'applicazione WinForm, è possibile utilizzare uno dei tanti oggetti Timer per eseguire effettivamente l'aggiornamento dell'interfaccia utente per quel particolare controllo, oppure è sufficiente tenere un contatore di quanti "letture" o "aggiornamenti" ai dati sottostanti (nel tuo caso, un flusso) e aggiorna solo l'interfaccia utente sul numero X di modifiche. Utilizzare l'opzione StringBuilder e gli aggiornamenti sfalsati è probabilmente la strada da percorrere.

+0

In così tante parole, questo è quello che ho fatto. Ho usato un StringBuilder e ho bufferizzato gli aggiornamenti a circa 1-200ms su un thread separato, e ora funziona molto meglio. Grazie! – qJake

1

La strategia di aggiornamento dell'interfaccia utente è il compito più difficile nelle applicazioni di elaborazione dati. Io uso il seguente schema:

  1. Lavorare thread sta eseguendo il lavoro e memorizza i risultati nei risultati di stoccaggio
  2. filo aggiornamento interfaccia utente è aggregando i risultati e aggiornamenti dell'interfaccia utente, se necessario,
3

Si potrebbe provare buffer : Invece di scrivere direttamente su TextBox e quindi scorrere, scrivere su un StringBuilder (assicurarsi di capire come farlo in un modo sicuro per i thread!) E disporre di un thread separato flush a TextBox in un intervallo fisso (ad esempio ogni secondo).

0

Uso un System.Windows.Forms.Timer per scrivere in batch in caselle di testo in blocchi da 50 ms. Uso una classe RingBuffer sicura dal thread come buffer tra i thread di scrittura e il thread del timer di modulo (thread ui). Non posso darti il ​​codice per questo, ma puoi sostituirlo con una coda con blocchi attorno ad esso, o forse una delle classi di raccolta simultanee.

Tweak per soddisfare le vostre esigenze.

/// <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; 
     } 
    } 
} 
Problemi correlati