2012-01-25 27 views
6

Với mã ví dụ sau:Ngăn chặn một hộp văn bản từ tụt do cập nhật nhanh

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

Khi bạn chạy mã này, sau khi vòng lặp và sợi chấm dứt, hộp văn bản vẫn được cập nhật (có lẽ vì đệm Invokes). Ứng dụng của tôi sử dụng logic tương tự để điền vào một hộp văn bản và tôi đang gặp vấn đề tương tự.

Câu hỏi của tôi là: Làm thế nào tôi có thể điền vào hộp văn bản này càng nhanh càng tốt, vẫn cuộn xuống cuối mỗi lần, và giảm/loại bỏ độ trễ này?

+2

Bạn có thể đọc nhanh không? Tôi không. Chỉ cần cập nhật các hộp văn bản mỗi x giây thay vì mỗi đánh dấu đồng hồ. –

+0

Đây chỉ là một ví dụ về cách tái sản xuất vấn đề (mặc dù hơi khắc nghiệt). Tôi thực sự đang đọc từ một luồng, vì vậy các bản cập nhật có thể nhanh hoặc chậm. – qJake

+0

Vẫn còn câu hỏi và đề xuất của tôi vẫn giữ nguyên. Giao diện người dùng là để phù hợp với người dùng. –

Trả lời

7

Có một vài tùy chọn bạn có thể thực hiện ở đây. Đầu tiên, bạn có thể thiết lập bộ đệm đôi trên biểu mẫu, điều này sẽ kết thúc vẽ tất cả các bản cập nhật trên một bitmap cơ bản, sau đó hiển thị hình ảnh mới được vẽ (thay vì vẽ các Điều khiển riêng lẻ trên một đối tượng đồ họa). Tôi thấy tốc độ tăng 50% với phương pháp này. Đưa nội dung này vào hàm tạo:

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

Điều khác cần lưu ý là ghép nối chuỗi là SLOW cho một lượng lớn dữ liệu. Bạn nên sử dụng một StringBuilder để xây dựng dữ liệu và sau đó chỉ hiển thị nó bằng cách sử dụng StringBuilder.ToString (mặc dù vẫn còn tốt hơn để tách rời các bản cập nhật, có thể một lần mỗi 100 lần lặp lại). Trên máy tính của tôi, chỉ cần thay đổi nó để nối thêm vào StringBuilder, nó đã đi từ 2,5 phút để chạy qua 10k lần lặp đến khoảng 1,5 phút. Tốt hơn, nhưng vẫn chậm.

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

Cuối cùng, chỉ cần thử nghiệm đáng kinh ngạc (ném một điều kiện duy nhất vào mã trên, ngay trước cuộc gọi Gọi) và kết thúc sau 2 giây. Vì chúng tôi đang sử dụng StringBuilder để thực sự xây dựng chuỗi, chúng tôi vẫn giữ lại tất cả dữ liệu, nhưng bây giờ chúng tôi chỉ phải thực hiện cập nhật 100 lần so với 10k lần.

Vì vậy, bây giờ, các tùy chọn của bạn là gì? Cho rằng đây là một ứng dụng WinForm, bạn có thể sử dụng một trong nhiều đối tượng Timer để thực sự cập nhật giao diện người dùng cho điều khiển cụ thể đó hoặc bạn có thể chỉ cần đếm số lượng "lần đọc" hoặc "cập nhật" cho dữ liệu cơ bản (trong trường hợp của bạn, một luồng) và chỉ cập nhật giao diện người dùng trên số lần thay đổi X. Bằng cách sử dụng cả hai tùy chọn StringBuilder và cập nhật so le có lẽ là con đường để đi.

+0

Trong rất nhiều từ, đây là những gì tôi đã làm. Tôi đã sử dụng một StringBuilder và đệm các bản cập nhật lên khoảng 1-200ms trên một chuỗi riêng biệt và bây giờ nó hoạt động tốt hơn rất nhiều. Cảm ơn! – qJake

1

Chiến lược cập nhật giao diện người dùng là nhiệm vụ khó khăn nhất trong các ứng dụng xử lý dữ liệu. tôi sử dụng mô hình sau:

  1. Làm việc thread được thực hiện công việc và lưu trữ kết quả trong lưu trữ kết quả
  2. UI cập nhật chủ đề đang tập hợp kết quả và cập nhật giao diện người dùng nếu cần thiết
3

Bạn có thể thử đệm : Thay vì viết trực tiếp vào TextBox và sau đó cuộn, hãy viết thư đến StringBuilder (đảm bảo bạn tìm ra cách thực hiện điều này theo cách an toàn) và có chủ đề riêng tuôn ra đến số TextBox trong khoảng thời gian cố định (giả sử mỗi giây).

0

Tôi sử dụng một System.Windows.Forms.Timer để ghi hàng loạt vào hộp văn bản trong khối 50 ms. Tôi sử dụng lớp RingBuffer an toàn chủ đề làm bộ đệm giữa các chuỗi ghi và chuỗi bộ đếm thời gian biểu mẫu (chuỗi chủ đề). Tôi không thể cung cấp cho bạn mã cho điều đó, nhưng bạn có thể thay thế nó bằng một hàng đợi có khóa xung quanh nó, hoặc có lẽ là một trong các lớp thu thập đồng thời.

Tinh chỉnh để phù hợp với nhu cầu của bạn.

/// <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; 
     } 
    } 
} 
Các vấn đề liên quan