Unresponsive UI when too fast and too much data needs to be updated
I made a control to log messages from different threads to screen. It uses rich text box to display formatted text.
When there are 20 threads which append their messages every 200-250ms the main UI becomes unresponsive for some time and after the messages waiting are processed the UI starts to response again. When the threads are running the moving of the window is not smooth.
Message writing to rich text box is synchronised with locks.
What can you suggest to improve the performance? I'm planning to run 100 threads.
Here is my code. I redirect the console output(s) to it and it logs everything that's going on and displays in formatted form inside a rich text box.
public void RedirectStandardOutput()
{
Console.SetOut(ConsoleStream);
System.Diagnostics.Debug.Listeners.Add(new System.Diagnostics.TextWriterTraceListener(Console.Out));
System.Diagnostics.Debug.AutoFlush = true;
}
After the console is redirected all Console.WriteLine("bla bla"); is written to screen.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using CoreLib.Parsers;
namespace ConsoleWidget
{
public class ConsoleStream : System.IO.TextWriter
{
private readonly object _textBoxLock = new object();
public RichTextBox TextBox { get; set; }
public List<TextFormat> TextFormatList { get; set; }
public bool AutoClear { get; set; }
public int AutoClearLength { get; set; }
public bool AutoSave { get; set; }
public string AutoSaveDir { get; set; }
public ConsoleStream()
{
TextFormatList = new List<TextFormat>();
}
public void AppendText(string text)
{
if (TextBox == null) return;
var textLength = TextBox.TextLength;
if (AutoClear && textLength > AutoClearLength)
{
if (AutoSave)
{
var dir = string.Format(@"{0}\{1}\{2}", Environment.CurrentDirectory, AutoSaveDir, CoreLib.Extensions.DateTimeExtensions.DateTimeNowDir);
if (!System.IO.Directory.Exists(dir))
System.IO.Directory.CreateDirectory(dir);
var path = string.Format(@"{0}\{1}.log", dir, CoreLib.Extensions.DateTimeExtensions.GetDateTimeNowFileName);
TextBox.SaveFile(path);
}
TextBox.Clear();
}
TextBox.AppendText(text);
// Format text.
foreach (var textFormat in TextFormatList)
{
int beginIndex;
int length;
if (textFormat.GetFormatProperties(text, out beginIndex, out length))
{
// RichTextBox counts newline "\r\n" which is double char as single char.
// Causes shifting in selection starts. The lines below count the "\r" chars before the beginIndex.
var leftText = text.Substring(0, beginIndex);
var newLineCount = leftText.Count(c => c == '\r');
TextBox.SelectionStart = textLength + beginIndex - newLineCount;
TextBox.SelectionLength = length;
if (!textFormat.Color.IsEmpty)
TextBox.SelectionColor = textFormat.Color;
if (textFormat.Font != null)
TextBox.SelectionFont = textFormat.Font;
开发者_开发百科 }
}
TextBox.ScrollToCaret();
}
public void Clear()
{
lock (_textBoxLock)
{
TextBox.Clear();
}
}
public int TextLength
{
get
{
lock (_textBoxLock)
{
return TextBox.TextLength;
}
}
}
public void SaveAs(string path)
{
lock (_textBoxLock)
{
TextBox.SaveFile(path);
}
}
public override Encoding Encoding
{
get { return Encoding.Default; }
}
public override void Write(string value)
{
if (TextBox == null) return;
var action = (Action)(() => AppendText(value));
lock (_textBoxLock)
{
if (TextBox.InvokeRequired)
TextBox.BeginInvoke(action);
else
action();
}
}
public override void WriteLine()
{
Write(NewLine);
}
public override void WriteLine(string value)
{
Write(value);
WriteLine();
}
}
}
Have them write to a buffer RichTextBox (one that's not actually part of your form) and only append the buffer to the UI RichTextBox every 250 ms or so.
Have your worker threads add their data to some sort of queue/list and then have the main thread add a batch of new data from the store of new data every second/half second (tune to fit your process).
Something basic like this would probably be fine:
public class DataStore<T>{
private object _lock = new object();
private List<T> _data = new List<T>();
public void Add(T data){
lock (_lock){
_data.Add(data);
}
}
public T[] TakeWork(){
T[] result;
lock (_lock){
result= _data.ToArray();
_data.Clear();
}
return result;
}
}
Just create a DataStore and have your work threads use the Add function to add work to be displayed then do
foreach (var s in _dataStore.TakeWork()){ richTextBox.AppendText(s); }
in a System.Windows.Forms.Timer event. You will probably want to trim the rich text box text as well though since your app will start to slow down if you just keep pumping data in all day....
Maybe you can try thread pool or task instead to manage your threads better.
精彩评论