Fast Append Text to text box

江枫思渺然 提交于 2019-12-23 04:11:08

问题


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#.


回答1:


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;
} 



回答2:


Maybe try http://sourceforge.net/projects/fastlogconsole - has excellent performance




回答3:


If you want realtime writes inside the textbox, why not using the synchronous Invoke instead of BeginInvoke which queues the function to be called later?




回答4:


Don't use BeginInvoke. Use Invoke instead. See Application Becomes Nonresponsive While Adding Thousands of Rows. That's not exactly the same issue (he's using a DataGridView rather than a textbox), but it's the same kind of thing. Your BackgroundWorker is starting up a whole bunch of asynchronous tasks. You're better off making it do one at a time.




回答5:


You could try using a RichTextBox control instead, and prevent it from refreshing its UI, except every so often. Kind of like the StringBuilder suggestion, but a little simpler. See this SO question for an example of how to do that,




回答6:


I used @Jeffre L. Whitledge's advice and reduce the number of string allocations. Since this is a closed application with a fixed number of strings, I cached the strings. This produced a side-effect of my program executing significantly faster.

One of my issues is still the slowness in Windows responding to messages. This can be seen when the progress bar is updated. There is a definite delay from when a message is sent (such as append text), and when it is performed.



来源:https://stackoverflow.com/questions/5490731/fast-append-text-to-text-box

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!