2

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

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
  • If you have adjusted any of the thread priorities in your application, then setting them all to Normal may fix this this problem. – Jeffrey L Whitledge Mar 30 '11 at 19:31
  • @Jeffrey: Unfortunately, I don't know how to set the priorities of the BackgroundWorker, GUI nor SerialPort; so I haven't changed them from their default settings. – Thomas Matthews Mar 30 '11 at 19:38

6 Answers6

1

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

Musznik
  • 94
  • 2
  • 5
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;
} 
Jeffrey L Whitledge
  • 58,241
  • 9
  • 71
  • 99
  • Is it possible to have Textbox use (C++ style) references to the strings rather than copying them? Or pointers? – Thomas Matthews Mar 30 '11 at 20:06
  • Also, is there any way to start the garbage collector to regain some of this fragmented memory? – Thomas Matthews Mar 30 '11 at 20:07
  • @Thomas Matthews - Re: C++ style, you might be able to do something with character arrays, unsafe code, and P/Invoke, but I thought you wanted a *simple* application! – Jeffrey L Whitledge Mar 30 '11 at 20:11
  • @Thomas Matthews - Re: garbage collector, The GC is very highly tuned, and it is unlikely that you can improve its operations. It will run when necessary, and it will automatically compact the allocated objects and defragment the memory. Unfortunately, this won't change the realities of the amount of memory being used. It is probably the GC operations that you are experiencing as "hangs" anyway. Forcing additional hangs isn't going to help any. – Jeffrey L Whitledge Mar 30 '11 at 20:13
  • I just tried this, and using the code you provided, it was just reusing the first part of the textbox.text, up to the maxDisplayTextLength, and then appending the new message on to that. I think you might have meant to use `previous.Remove()` to chop off the needed space, then append the new time_stamp_text to that, instead of a substring from the beginning of the textbox.text. – Zack Aug 27 '13 at 20:51
0

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?

David Pratte
  • 1,036
  • 8
  • 14
  • The `BackgroundWorker` thread is stuck at that `Invoke` method (when I pause the debugger, this is the line that showed up). Here is the invocation: `m_textbox.Invoke(new write_debug_text_callback(this.write_debug_text), new object[] { debug_text });` – Thomas Matthews Mar 30 '11 at 19:07
  • Do you have a breakpoint set in write_debug_text? If that is the case, Visual Studio will show you that the UI thread is stopped there. The BackgroundWorker thread will be stuck at Invoke because write_debug_text has to complete before it can do anything else. – David Pratte Mar 30 '11 at 19:13
  • Looks like I may have a dead-lock since `Invoke`, is waiting for the UI thread to finish, but the UI thread is not getting a chance to run. – Thomas Matthews Mar 30 '11 at 19:27
0

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.

Community
  • 1
  • 1
Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
0

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,

Community
  • 1
  • 1
Steve Danner
  • 21,818
  • 7
  • 41
  • 51
0

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.

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
  • To maintain responsiveness in the GUI window, there is an overload of BeginInvoke which accepts a DispatcherPriority, if you set it lower than 'Input' such as 'Background', the BeginInvoke calls will not interfere with user input responsiveness. – Sogger Feb 08 '12 at 23:09