Dispatcher.Invoke循环冻结UI

时间:2012-06-14 17:34:13

标签: wpf textbox backgroundworker dispatcher

我有一个日志拖尾应用程序,它在不定式循环中执行后台工作线程并检查某些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,当它经常发生接口冻结时

4 个答案:

答案 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)

抱歉,但是无限循环地阅读文件既错误又......嗯......愚蠢。 有像FileSystemWatcher这样明确定义的类,没有必要为你的CPU核心提供100%的负载只是因为你想要实时更新你的文件。

我认为你对编程很陌生 - 好吧,我们都犯错误,必须学习,接受这个建议:

  • 无限循环自动编程错误
  • 如果确实需要循环,建议在每次迭代之间让相应的线程睡眠

答案 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。