Given the following example code:
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 thread terminate, the text box is still updating (presumably because of buffered Invokes). My application uses similar logic to fill a text box, and I'm having the same problem.
My question is: How can I fill this text box as fast as possible, still scroll to the bottom every time, and yet reduce/eliminate this lag?
There are a few options you can take here. First, you can set double buffering on the form, which will end up drawing all the updates on an underlying bitmap, which then displays the newly drawn image (instead of individually drawing Controls on a graphics object). I saw about a 50% speed increase with this method. Throw this into the constructor:
this.SetStyle(
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint |
ControlStyles.DoubleBuffer,true);
The other thing to keep in mind is that string concatenation is SLOW for large amounts of data. You're better off using a StringBuilder to build the data and then just show it using StringBuilder.ToString (although still better to stagger the updates, maybe once every 100 iterations). On my machine, just changing it to append to the StringBuilder, it went from 2.5 minutes to run through 10k iterations to about 1.5 minute. 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 tested out staggering (threw a single conditional into the above code, right before the Invoke call), and it finished in 2 seconds. Since we're using the StringBuilder to actually build the string, we still retain all the data, but now we only have to do the updates 100 times as opposed to 10k times.
So now, what are your options? Given that this is a WinForm application, you can utilize one of the many Timer objects to actually perform the UI update for that particular control, or you can just keep a counter of how many "reads" or "updates" to the underlying data (in your case, a stream) and only update UI on X number of changes. Utilizing both the StringBuilder option and staggered updates is probably the way to go.
You could try buffering: Instead of writing directly to the TextBox
and then scrolling, write to a StringBuilder
(make sure you figure out how to do this in a thread-safe way!) and have a separate thread flush to the TextBox
in a fixed interval (say every second).
UI updating strategy is the most difficult task in data processing applications. I use the following pattern:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With