Fast Append Text to text box

Thomas Matthews picture Thomas Matthews · Mar 30, 2011 · Viewed 9.3k times · Source

I have a BackgroundWorker thread that is posting messages, using BeginInvoke on a textbox in the GUI. The method, write_debug_text, that displays text in the textbox uses AppendText and also writes the text to the Console.

The appearance is that the BackgroundWorker is writing too fast for the write_debug_text to keep up. I set a breakpoint at write_debug_text and have to wait a long time before it is hit. Many calls to 'BeginInvoke` occur before the breakpoint is hit.

I'm looking for a real-time display of messages on the UI, much like the System.Console in the VS C# Express IDE.

From searching on SO, I understand that AppendText is the faster method to use and that strings may have to be reallocated.

Some replies suggest using StringBuilder then periodically writing that text to the textbox. But this requires adding more events and timers; which I would rather not do (my simple application keeps getting more and more complex).

How can I either write real-time to the Textbox (and have it display)?

My current idea is to create a widget inheriting from Textbox that uses a text queue and timer.

Edit 1: Sample code

Here is a fragment of my code:

    private m_textbox;
    //...
    m_textbox.BeginInvoke(new write_debug_text_callback(this.write_debug_text),
                                      new object[] { debug_text });
    return;

private void write_debug_text(string text)
{
    string time_stamp_text = "";
    time_stamp_text = DateTime.Now.ToString() + "   ";
    time_stamp_text += text;
    m_textbox.AppendText(time_stamp_text);
    m_textbox.Update();
    System.Console.Write(time_stamp_text);
    return;
}

I have tried changing BeginInvoke to Invoke and my application is hung. When I pause / break using the debugger, the execution pointer is on the call to Invoke.

BTW, I've got many years experience with Java, C++, and C. I am on my 5th month with C#.

Answer

Jeffrey L Whitledge picture Jeffrey L Whitledge · Mar 30, 2011

If there are a great many messages that are being displayed, then the problem is likely to be memory allocation issues.

Let's assume that each message is 30 characters long. This would be (roughly) 60 bytes. Let's further assume that you are adding 10 messages per second. Then, in the first second the generated strings will be: 60 + 120 + 60 + 180 + 60 + 240 + 60 + 300 + ... + 60 + 600 = 3840 bytes. In the second second the total rises to 13,740 bytes. In the third second: 29,640. 4th: 51,540. ... 10th: 308,940 bytes.

After 18 seconds, one megabyte is reached and the displayed strings are 11 kb each.

At the one minute mark we are at 10 Mb of string allocated. At two minutes 43 Mb are devoted to these messages, and the string sizes are increasing through 71 kb each. After three minutes the messages are over 100 kb in size, and nearly 100 Mb are devoted to them.

This is why StringBuilder is so important for building long strings!

But since your plan is to display each intermediate step, StringBuilder won't help you here. This GUI requires that you generate a metric-boat-load of strings.

One possible solution to this problem is to chop off the front of the strings as you are inserting data into the back. You will still be allocating a lot of memory to strings, but the individual strings themselves will be of bounded size, and, thus, it will be much easier for the runtime to find a place for them in memory, and the rate of allocation will go way down as well.

This should reduce your garbage-collection pressure:

private const int maxDisplayTextLength = 5000;

private void write_debug_text(string text)
{
    string time_stamp_text = "";
    time_stamp_text = DateTime.Now.ToString() + "   " + text;
    string previous = m_textbox.Text;
    if (previous.Length + time_stamp_text.Length > maxDisplayTextLength)
         m_textbox.Text = previous.Substring(0, maxDisplayTextLength - time_stamp_text.Length) + time_stamp_text;
    else
         m_textbox.Text = previous + time_stamp_text;
    m_textbox.Update();
    System.Console.Write(time_stamp_text);
    return;
}