2012-01-25 10 views
6

zważywszy na poniższym przykładzie kodu:Prevent pole tekstowe z opóźniony z powodu szybkich aktualizacjach

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

Po uruchomieniu tego kodu, po pętli i nici wypowiedzenia, pole tekstowe jest ciągle uaktualniania (przypuszczalnie z powodu buforowane Wywołuje). Moja aplikacja używa podobnej logiki, aby wypełnić pole tekstowe i mam ten sam problem.

Moje pytanie brzmi: jak mogę wypełnić to pole tekstowe tak szybko, jak to możliwe, nadal przewijać do dołu za każdym razem, a jednak zmniejszyć/wyeliminować to opóźnienie?

+2

Czy potrafisz czytać tak szybko? Nie jestem. Wystarczy zaktualizować pole tekstowe co x sekund zamiast każdego taktu zegara. –

+0

To był tylko przykład, jak odtworzyć problem (choć trochę skrajny). W rzeczywistości czytam ze strumienia, więc aktualizacje mogą nadejść szybko lub wolno. – qJake

+0

Nadal moje pytanie i sugestia pozostają takie same. Interfejs użytkownika jest przeznaczony dla użytkownika. –

Odpowiedz

7

Istnieje kilka opcji, które można wybrać tutaj. Po pierwsze, możesz ustawić podwójne buforowanie na formularzu, które zakończy rysowanie wszystkich aktualizacji na bazowej mapie bitowej, która wyświetli nowo narysowany obraz (zamiast pojedynczego rysowania elementów sterujących na obiekcie graficznym). Widziałem około 50% wzrost prędkości za pomocą tej metody. Rzuć to do konstruktora:

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

Inną rzeczą, aby pamiętać, że ciąg konkatenacji jest powolny do dużych ilości danych. Lepiej jest użyć StringBuilder do zbudowania danych, a następnie po prostu pokazać je przy użyciu StringBuilder.ToString (choć nadal lepiej rozłożyć aktualizacje, może raz na 100 iteracji). Na moim komputerze, po prostu zmieniając go, aby dołączyć do StringBuilder, trwało od 2,5 minuty do uruchomienia iteracji 10k do około 1,5 minuty. Lepiej, ale wciąż wolno.

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

Wreszcie tylko przetestowane zataczając (rzucił jeden warunkowy w powyższym kodzie, tuż przed wywołaniem Invoke), a zakończył w 2 sekundy. Ponieważ używamy StringBuilder do budowania łańcucha, wciąż przechowujemy wszystkie dane, ale teraz musimy tylko aktualizować 100 razy, a nie 10k razy.

Jakie są teraz opcje? Biorąc pod uwagę, że jest to aplikacja WinForm, można wykorzystać jeden z wielu obiektów Timera, aby faktycznie wykonać aktualizację interfejsu dla tej konkretnej kontrolki, lub po prostu zachować licznik liczby "odczytów" lub "aktualizacji" do danych podstawowych (w twoim przypadku strumień) i aktualizuj tylko UI dla X liczby zmian. Wykorzystanie zarówno opcji StringBuilder, jak i zmian naprzemiennych jest prawdopodobnie drogą do zrobienia.

+0

W tak wielu słowach, to właśnie zrobiłem. Użyłem StringBuilder i buforowałem aktualizacje do około 1-200 ms w osobnym wątku i teraz działa o wiele lepiej. Dzięki! – qJake

1

Aktualizacja strategii UI to najtrudniejsze zadanie w aplikacjach do przetwarzania danych. używam następujący wzór:

  1. Praca wątek jest wykonywanie pracy i przechowuje wyniki w wynikach przechowywania
  2. UI aktualizacji wątek sumowaniu wyników i aktualizacji UI razie potrzeby
3

Można spróbować buforowanie : Zamiast pisać bezpośrednio do TextBox, a następnie przewijać, napisz do StringBuilder (upewnij się, że wymyślisz, jak to zrobić w sposób bezpieczny dla wątków!) I masz osobny wątek flush do TextBox w ustalonych odstępach czasu (na przykład co sekundę).

0

Używam System.Windows.Forms.Timer do wsadowego zapisu do pól tekstowych w porcjach 50 ms. Używam klasy RingBuffer bezpiecznej dla wątków jako bufora między wątkami do pisania a wątkiem licznika formularzy (wątek interfejsu użytkownika). Nie mogę podać kodu, ale można go zastąpić kolejką z blokadami wokół niej lub jedną z klas zbieżnych.

Dostosuj się do swoich potrzeb.

/// <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; 
     } 
    } 
} 
Powiązane problemy