给出以下示例代码:
new Thread(() => { for(int i = 0; i < 10000; i++) { Invoke((MethodInvoker)() => { myTextBox.Text += DateTime.Now.ToString() + "\r\n"; myTextBox.SelectedIndex = myTextBox.Text.Length; myTextBox.ScrollToCarat(); }); } }).Start();
运行此代码时,在循环和线程终止后,文本框仍在更新(可能是因为缓冲的Invokes)。我的应用程序使用类似的逻辑来填充文本框,我遇到了同样的问题。
我的问题是:如何尽可能快地填写此文本框,每次仍然滚动到底部,然后减少/消除这种滞后?
答案 0 :(得分:8)
您可以在这里选择几个选项。首先,您可以在窗体上设置双缓冲,最终将在底层位图上绘制所有更新,然后显示新绘制的图像(而不是在图形对象上单独绘制控件)。我用这种方法看到了大约50%的速度提升。把它扔进构造函数:
this.SetStyle(
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint |
ControlStyles.DoubleBuffer,true);
要记住的另一件事是字符串连接对于大量数据来说是SLOW。你最好使用StringBuilder来构建数据,然后使用StringBuilder.ToString显示它(尽管更好地错开更新,可能每100次迭代一次)。在我的机器上,只需更改它以附加到StringBuilder,它从2.5分钟开始运行10k迭代到大约1.5分钟。更好,但仍然很慢。
new System.Threading.Thread(() =>
{
for(int i = 0; i < 10000; i++)
{
sb.AppendLine(DateTime.Now.ToString());
Invoke((Action)(() =>
{
txtArea.Text = sb.ToString();
txtArea.SelectionStart = txtArea.Text.Length;
txtArea.ScrollToCaret();
}));
}
}).Start();
最后,刚刚测试出惊人的结果(在Invoke调用之前将一个条件输入上面的代码),并在2秒内完成。由于我们使用StringBuilder实际构建字符串,我们仍然保留所有数据,但现在我们只需要进行100次更新,而不是10k次。
现在,你有什么选择?鉴于这是一个WinForm应用程序,您可以利用众多Timer对象中的一个来实际执行该特定控件的UI更新,或者您可以只保留对底层数据“读取”或“更新”的计数器(在您的情况下,一个流)并且只更新X个更改的UI。利用StringBuilder选项和交错更新可能是最佳选择。
答案 1 :(得分:3)
你可以尝试缓冲:不要直接写入TextBox
然后滚动,而是写一个StringBuilder
(确保你弄明白如何在线程安全的方式!)并在固定的时间间隔内(例如每秒)为TextBox
提供一个单独的线程 flush 。
答案 2 :(得分:1)
UI更新策略是数据处理应用程序中最困难的任务。 我使用以下模式:
答案 3 :(得分:0)
我使用System.Windows.Forms.Timer以50毫秒的块批量写入文本框。我使用线程安全的RingBuffer类作为写线程和表单计时器线程(ui线程)之间的缓冲区。我无法为您提供代码,但您可以将其替换为带有锁的队列,或者可能是其中一个并发集合类。
调整以满足您的需求。
/// <summary>
/// Ferries writes from a non-UI component to a TextBoxBase object. The writes originate
/// on a non-UI thread, while the destination TextBoxBase object can only be written
/// from the UI thread.
///
/// Furthermore, we want to batch writes in ~50 ms chunks so as to write to the UI as little as
/// possible.
///
/// This classes uses a Forms Timer (so that the timer fires from the UI thread) to create
/// write chunks from the inter-thread buffer to write to the TextBoxBase object.
/// </summary>
public class TextBoxBuffer
{
private RingBuffer<string> buffer;
private TextBoxBase textBox;
private System.Windows.Forms.Timer formTimer;
StringBuilder builder;
public TextBoxBuffer( TextBoxBase textBox )
{
this.textBox = textBox;
buffer = new RingBuffer<string>( 500 );
builder = new StringBuilder( 500 );
this.formTimer = new System.Windows.Forms.Timer();
this.formTimer.Tick += new EventHandler( formTimer_Tick );
this.formTimer.Interval = 50;
}
public void Start()
{
this.formTimer.Start();
}
public void Shutdown()
{
this.formTimer.Stop();
this.formTimer.Dispose();
}
public void Write( string text )
{
buffer.EnqueueBlocking( text );
}
private void formTimer_Tick( object sender, EventArgs e )
{
while( WriteChunk() ) {}
Trim();
}
/// <summary>
/// Reads from the inter-thread buffer until
/// 1) The buffer runs out of data
/// 2) More than 50 ms has elapsed
/// 3) More than 5000 characters have been read from the buffer.
///
/// And then writes the chunk directly to the textbox.
/// </summary>
/// <returns>Whether or not there is more data to be read from the buffer.</returns>
private bool WriteChunk()
{
string line = null;
int start;
bool moreData;
builder.Length = 0;
start = Environment.TickCount;
while( true )
{
moreData = buffer.Dequeue( ref line, 0 );
if( moreData == false ) { break; }
builder.Append( line );
if( Environment.TickCount - start > 50 ) { break; }
if( builder.Length > 5000 ) { break; }
}
if( builder.Length > 0 )
{
this.textBox.AppendText( builder.ToString() );
builder.Length = 0;
}
return moreData;
}
private void Trim()
{
if( this.textBox.TextLength > 100 * 1000 )
{
string[] oldLines;
string[] newLines;
int newLineLength;
oldLines = this.textBox.Lines;
newLineLength = oldLines.Length / 3;
newLines = new string[newLineLength];
for( int i = 0; i < newLineLength; i++ )
{
newLines[i] = oldLines[oldLines.Length - newLineLength + i];
}
this.textBox.Lines = newLines;
}
}
}
答案 4 :(得分:0)
首先,您需要将DoubleBuffered
设置为true
。
设备以60 / fps(每秒帧)的速度运行。
您可以创建计时器并将间隔设置为至少17:
timer1.Interval = 17
这将每秒执行近59次更新。这样可以避免滞后。
祝你好运!