tucotraff
tucotraff

Reputation: 61

Optimize an invoke logging function - multithreading

My application works with a lot of threads, and each threads use my AddDetailLog function to add the progress of the thread in a global textBox, so I can follow how each thread are progressing. This function can be called more than 500 times per second, and when I used the VS2015 Profiler, I've seen that it's this function who use 50% of my CPU. When I load more than 150 thread, my CPU is at 100%, so I really need to optimize this function.

The function to optimize with multithreading :

public void AddDetailLog(string text)
    {
        if(Program.SHOWDETAILSLOG)
        {
            if (this.textBox_log.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(AddDetailLog);
                this.Invoke(d, new object[] { text });
            }
            else
                this.textBox_log.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "] - " + text + Environment.NewLine);
        }
    }

because of the multithreading thing, I need to check if my textBox need an invoke, because all of the threads (not backgroundWorker) are acting on the same textBox on my global form.

If someone have any idea to optimize this function, I will be glad to ear it.

Thanks

Upvotes: 0

Views: 56

Answers (1)

KeithS
KeithS

Reputation: 71565

First off, Control.Invoke() blocks the calling process, ultimately defeating the purpose of your background threads as they'll end up synchronized through the GDI message pump. You can use BeginInvoke() instead to make the call asynchronously, thus allowing your background process to continue on.

Second, writing into a TextBox control at that rate is going to slow you down, because any change to TextBox.Text will fire TextChanged, which among other things will redraw the control with the updated text. Do that 500 times a second, especially with any custom TextChanged handlers like code to scroll the textbox to the bottom, and you'll be char-grilling the UI thread pretty crispy.

Instead, why not have your logger write entries into a simpler in-memory object, and then have a Timer executing on your form to retrieve, concatenate and display those messages in the TextBox control? With high concurrency in mind, I'd recommend a ConcurrentQueue<string>. Your background workers perform "TryAdd" calls to enqueue log entries, then your UI Timer.Tick handler dequeues them with TryTake and appends them to the TextBox in a more UI-friendly way (like maybe concatenating entries in batches of 10 before appending to the TextBox).

Lastly, the longer the content of the TextBox, the longer it will take to render, because the TextBox has to calculate the display size of the string in order to render scrollbars and show the portion of the string that should actually display. I would truncate the "back end" of your log display after 30k characters or so. To avoid losing potentially valuable information, the same handler can pass the messages to a StreamWriter writing everything to a file; you can add another level of asynchronous processing here to keep the UI responsive.

Upvotes: 3

Related Questions