Prevent text window lag due to fast updates

Given the following code example:

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

When you run this code, after the loop and stream end, the text field is still updated (presumably due to Invokes buffering). My application uses the same logic to populate a text box, and I have the same problem.

My question is: how can I fill out this text box as quickly as possible, still scroll down every time and still reduce / eliminate this lag?

+7
source share
5 answers

There are several options you can take here. First, you can set up double buffering in a form that finishes drawing all updates on the underlying bitmap, and then displays the newly drawn image (instead of individually drawing controls on a graphic object). With this method, I saw a speed increase of 50%. Throw this into the constructor:

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

Another thing to keep in mind is that string concatenation is SLOW for large amounts of data. You better use StringBuilder to create the data, and then just show it with StringBuilder.ToString (although it’s even better to deploy updates, perhaps every 100 iterations). On my machine, just modifying it to add to StringBuilder, it lasted from 2.5 minutes to iterate 10k to about 1.5 minutes. Better, but still slow.

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

Finally, just a proven stunning (threw one conditional code into the above code, just before invoke invocation), and it finished in 2 seconds. Since we use StringBuilder to actually build the string, we still save all the data, but now we only need to make updates 100 times, not 10k times.

So what are your options? Given that this is a WinForm application, you can use one of the many Timer objects to actually perform user interface updates for that particular control, or you can just keep a count of the number of β€œreads” or β€œupdates” for the underlying data (in your case, a stream) and only update the UI by X the number of changes. Probably using the StringBuilder option and incremental updates is the way to go.

+8
source

You can try buffering: instead of writing directly to a TextBox and then scrolling, write to StringBuilder (make sure you figured out how to do this in a thread-safe way!) And have a separate thread flush with the TextBox at a fixed interval (say, every second).

+3
source

The UI update strategy is the most difficult task in data processing applications. I am using the following pattern:

  • The workflow does the work and saves the results to the results store.
  • Updating the user interface stream consolidates the results and updates the user interface as necessary
+1
source

I am using System.Windows.Forms.Timer to batch write to text fields with 50 ms chunks. I use the thread-safe RingBuffer class as a buffer between write streams and a form timer thread (ui thread). I cannot give you the code for this, but you can replace it with a queue with locks around it, or perhaps one of the classes in the parallel collection.

Customize to your needs.

 /// <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; } } } 
0
source

First of all, you need to set DoubleBuffered to true .

Devices operate at a speed of 60 frames per second (frame per second).

You can create a timer and set the interval to at least 17:

 timer1.Interval = 17 

This will perform almost 59 updates per second. This way you can avoid lag.

Good luck

0
source

Source: https://habr.com/ru/post/906864/


All Articles