我有一个日志拖尾应用程序,它在不定式循环中执行后台工作线程并检查某些TXT文件中的最新条目。找到新条目后,我使用Dispatcher.Invoke更新屏幕上的TextBox,并将最新条目添加到文本文件中。
问题是,如果源文本文件每毫秒不断更新,那么用户界面就会冻结,因为Dispatcher.Invoke经常更新文本框。
不知道是否有任何解决方法。我可以从文本文件中获取更大的块但不想破坏日志拖尾的实时体验,也不想延迟通过UI线程将行写入TextBox,因为这会使我的应用程序与实际数据不同步源文本文件。
这是后台工作者DoWork方法
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
reader = new StreamReader(new FileStream(file.File, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
lastMaxOffset = reader.BaseStream.Length;
while (true)
{
if (worker.CancellationPending)
{
e.Cancel = true;
break;
}
if (reader.BaseStream.Length == lastMaxOffset)
continue;
reader.BaseStream.Seek(lastMaxOffset, SeekOrigin.Begin);
string line = "";
while ((line = reader.ReadLine()) != null)
this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)(() =>
{
textLog.Text += "\r" + line;
scrollViewer.ScrollToBottom();
}));
lastMaxOffset = reader.BaseStream.Position;
}
}
正如你所看到的,UI线程只是简单地将文本附加到TextBox,当它经常发生接口冻结时
答案 0 :(得分:7)
使用Dispatcher.Invoke
是一个同步调用,因此它实际上与在UI线程上执行此操作相同,因为对Invoke
的调用将阻塞,直到UI执行所请求的任务。如果你在短时间内做得太多,那么你就有效地阻止了UI线程。
相反,您应该使用Dispatcher.BeginInvoke
将UI线程的工作排队,但不会阻止。这会稍微好一点,就好像你在短时间内做得太多,你仍然在工作中充斥着UI线程,这样做会花费大量时间来完成这项工作。
相反,最好的方法是将这些更改排队到UI线程,然后当队列达到定义的限制(即100个新的文本行)或超过特定的时间量(比如200ms)时调用Dispatcher.BeginInvoke
将这些更改发送到UI。这将为您提供最佳的UI响应能力。
答案 1 :(得分:1)
通过为您读取的每一行调用Dispatcher.Invoke
,您实际上会导致每一行将数据推送回UI线程,并等待它完成。
这可能会使整个例程更慢而不是直接使用UI线程,因为你增加了开销,但没有把大部分工作都拉到后台线程中。
为了加快速度,您需要做一些缓冲数据的事情,并以更大的块发送它。在这种情况下,由于您正在寻找新的日志条目,我建议您一次读取文件末尾的所有日志条目,并将整个块组合回您的UI(而不是逐行进行) )。
答案 2 :(得分:1)
我认为你对编程很陌生 - 好吧,我们都犯错误,必须学习,接受这个建议:
答案 3 :(得分:0)
我同意CodingGorilla的回答。
使用Dispatcher.Invoke将导致同步调用。
这与直接在UI线程上进行调用的结果相同。
调用将阻塞,直到UI执行所请求的任务,如果这种情况连续快速发生,您的UI线程将会阻塞,因此您将无法获得更新。
尝试用 Dispatcher.BeginInvoke 替换 Dispatcher.Invioke ,看看这是否解决了您的问题。
// Load in background
this.Dispatcher.BeginInvoke(new Action(() =>
{
textLog.Text += "\r" + line;
scrollViewer.ScrollToBottom();
}));
同样建议您可以将循环置于睡眠状态1毫秒;使用 Thread.Sleep(1);每隔一段时间这样你最终不会占用cpu。